Browse Source

Merge pull request #575 from Seldaek/newlinks

New link handling, removed recommend, added require-dev, and changed suggest
Nils Adermann 13 years ago
parent
commit
65999c48e1
35 changed files with 567 additions and 343 deletions
  1. 3 0
      CHANGELOG.md
  2. 2 2
      composer.json
  3. 6 9
      doc/03-cli.md
  4. 22 5
      doc/04-schema.md
  5. 3 3
      res/composer-schema.json
  6. 8 5
      src/Composer/Command/DependsCommand.php
  7. 2 4
      src/Composer/Command/InstallCommand.php
  8. 3 5
      src/Composer/Command/UpdateCommand.php
  9. 0 12
      src/Composer/DependencyResolver/Solver.php
  10. 9 6
      src/Composer/Factory.php
  11. 114 90
      src/Composer/Installer.php
  12. 18 12
      src/Composer/Installer/InstallationManager.php
  13. 14 11
      src/Composer/Installer/InstallerInstaller.php
  14. 10 4
      src/Composer/Installer/InstallerInterface.php
  15. 16 18
      src/Composer/Installer/LibraryInstaller.php
  16. 11 25
      src/Composer/Installer/MetapackageInstaller.php
  17. 7 11
      src/Composer/Package/AliasPackage.php
  18. 5 6
      src/Composer/Package/BasePackage.php
  19. 9 4
      src/Composer/Package/Dumper/ArrayDumper.php
  20. 7 3
      src/Composer/Package/Loader/ArrayLoader.php
  21. 30 14
      src/Composer/Package/Locker.php
  22. 7 7
      src/Composer/Package/MemoryPackage.php
  23. 7 9
      src/Composer/Package/PackageInterface.php
  24. 31 0
      src/Composer/Repository/RepositoryManager.php
  25. 5 4
      tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php
  26. 5 4
      tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php
  27. 5 4
      tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php
  28. 20 15
      tests/Composer/Test/Installer/InstallationManagerTest.php
  29. 8 10
      tests/Composer/Test/Installer/InstallerInstallerTest.php
  30. 17 19
      tests/Composer/Test/Installer/LibraryInstallerTest.php
  31. 6 6
      tests/Composer/Test/Installer/MetapackageInstallerTest.php
  32. 68 13
      tests/Composer/Test/Package/Dumper/ArrayDumperTest.php
  33. 85 0
      tests/Composer/Test/Package/Loader/ArrayLoaderTest.php
  34. 3 2
      tests/Composer/Test/Package/LockerTest.php
  35. 1 1
      tests/Composer/Test/Repository/FilesystemRepositoryTest.php

+ 3 - 0
CHANGELOG.md

@@ -1,5 +1,8 @@
 * 1.0.0-alpha3
 
+  * Schema: Added 'require-dev' for development-time requirements (tests, etc), install with --dev
+  * Schema: Removed 'recommend'
+  * Schema: 'suggest' is now informational and can use any description for a package, not only a constraint
   * Added caching of repository metadata (faster startup times & failover if packagist is down)
   * Added include_path support for legacy projects that are full of require_once statements
   * Added installation notifications API to allow better statistics on Composer repositories

+ 2 - 2
composer.json

@@ -25,8 +25,8 @@
         "symfony/finder": "2.1.*",
         "symfony/process": "2.1.*"
     },
-    "recommend": {
-        "ext-zip": "*"
+    "suggest": {
+        "ext-zip": "Enabling the zip extension allows you to zip archives, and allows gzip compression of all internet traffic"
     },
     "autoload": {
         "psr-0": { "Composer": "src/" }

+ 6 - 9
doc/03-cli.md

@@ -39,11 +39,9 @@ resolution.
 * **--dry-run:** If you want to run through an installation without actually
   installing a package, you can use `--dry-run`. This will simulate the
   installation and show you what would happen.
-* **--no-install-recommends:** By default composer will install all packages
-  that are referenced by `recommend`. By passing this option you can disable
-  that.
-* **--install-suggests:** The packages referenced by `suggest` will not be
-  installed by default. By passing this option, you can install them.
+* **--dev:** By default composer will only install required packages. By
+  passing this option you can also make it install packages referenced by
+  `require-dev`.
 
 ## update
 
@@ -59,8 +57,7 @@ into `composer.lock`.
 
 * **--prefer-source:** Install packages from `source` when available.
 * **--dry-run:** Simulate the command without actually doing anything.
-* **--no-install-recommends:** Do not install packages referenced by `recommend`.
-* **--install-suggests:** Install packages referenced by `suggest`.
+* **--dev:** Install packages listed in `require-dev`.
 
 ## search
 
@@ -111,8 +108,8 @@ specific version.
 ## depends
 
 The `depends` command tells you which other packages depend on a certain
-package. You can specify which link types (`require`, `recommend`, `suggest`)
-should be included in the listing.
+package. You can specify which link types (`require`, `require-dev`)
+should be included in the listing. By default both are used.
 
     $ php composer.phar depends --link-type=require monolog/monolog
 

+ 22 - 5
doc/04-schema.md

@@ -41,7 +41,7 @@ Required for published packages (libraries).
 
 A short description of the package. Usually this is just one line long.
 
-Optional but recommended.
+Required for published packages (libraries).
 
 ### version
 
@@ -165,14 +165,13 @@ An example:
 
 Optional, but highly recommended.
 
-### Link types <span>(require, recommend, suggest, replace, provide)</span>
+### Package links <span>(require, require-dev, conflict, replace, provide)</span>
 
 Each of these takes an object which maps package names to version constraints.
 
 * **require:** Packages required by this package.
-* **recommend:** Recommended packages, installed by default.
-* **suggest:**  Suggested packages. These are displayed after installation,
-  but not installed by default.
+* **require-dev:** Packages required for developing this package, or running
+  tests, etc. They are installed if install or update is ran with `--dev`.
 * **conflict:** Mark this version of this package as conflicting with other
   packages.
 * **replace:** Packages that can be replaced by this package. This is useful
@@ -193,6 +192,24 @@ Example:
 
 Optional.
 
+### suggest
+
+Suggested packages that can enhance or work well with this package. These are
+just informational and are displayed after the package is installed, to give
+your users a hint that they could add more packages, even though they are not
+strictly required.
+
+The format is like package links above, except that the values are free text
+and not version constraints.
+
+Example:
+
+    {
+        "suggest": {
+            "monolog/monolog": "Allows more advanced logging of the application flow"
+        }
+    }
+
 ### autoload
 
 Autoload mapping for a PHP autoloader.

+ 3 - 3
res/composer-schema.json

@@ -90,14 +90,14 @@
             "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.",
             "additionalProperties": true
         },
-        "recommend": {
+        "require-dev": {
             "type": "object",
-            "description": "This is a hash of package name (keys) and version constraints (values) that this package recommends to be installed (typically this will be installed as well).",
+            "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
             "additionalProperties": true
         },
         "suggest": {
             "type": "object",
-            "description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).",
+            "description": "This is a hash of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).",
             "additionalProperties": true
         },
         "config": {

+ 8 - 5
src/Composer/Command/DependsCommand.php

@@ -25,7 +25,10 @@ use Symfony\Component\Console\Output\OutputInterface;
  */
 class DependsCommand extends Command
 {
-    protected $linkTypes = array('require', 'recommend', 'suggest');
+    protected $linkTypes = array(
+        'require' => 'requires',
+        'require-dev' => 'devRequires',
+    );
 
     protected function configure()
     {
@@ -34,7 +37,7 @@ class DependsCommand extends Command
             ->setDescription('Shows which packages depend on the given package')
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
-                new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', $this->linkTypes)
+                new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes))
             ))
             ->setHelp(<<<EOT
 Displays detailed information about where a package is referenced.
@@ -81,10 +84,10 @@ EOT
             foreach ($repository->getPackages() as $package) {
                 foreach ($types as $type) {
                     $type = rtrim($type, 's');
-                    if (!in_array($type, $this->linkTypes)) {
-                        throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', $this->linkTypes));
+                    if (!isset($this->linkTypes[$type])) {
+                        throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($this->linkTypes)));
                     }
-                    foreach ($package->{'get'.$type.'s'}() as $link) {
+                    foreach ($package->{'get'.$this->linkTypes[$type]}() as $link) {
                         if ($link->getTarget() === $needle) {
                             if ($verbose) {
                                 $references[] = array($type, $package, $link);

+ 2 - 4
src/Composer/Command/InstallCommand.php

@@ -32,8 +32,7 @@ class InstallCommand extends Command
             ->setDefinition(array(
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
-                new InputOption('no-install-recommends', null, InputOption::VALUE_NONE, 'Do not install recommended packages (ignored when installing from an existing lock file).'),
-                new InputOption('install-suggests', null, InputOption::VALUE_NONE, 'Also install suggested packages (ignored when installing from an existing lock file).'),
+                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
             ))
             ->setHelp(<<<EOT
 The <info>install</info> command reads the composer.json file from the
@@ -57,8 +56,7 @@ EOT
             ->setDryRun($input->getOption('dry-run'))
             ->setVerbose($input->getOption('verbose'))
             ->setPreferSource($input->getOption('prefer-source'))
-            ->setInstallRecommends(!$input->getOption('no-install-recommends'))
-            ->setInstallSuggests($input->getOption('install-suggests'))
+            ->setDevMode($input->getOption('dev'))
         ;
 
         return $install->run() ? 0 : 1;

+ 3 - 5
src/Composer/Command/UpdateCommand.php

@@ -30,8 +30,7 @@ class UpdateCommand extends Command
             ->setDefinition(array(
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
-                new InputOption('no-install-recommends', null, InputOption::VALUE_NONE, 'Do not install recommended packages.'),
-                new InputOption('install-suggests', null, InputOption::VALUE_NONE, 'Also install suggested packages.'),
+                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
             ))
             ->setHelp(<<<EOT
 The <info>update</info> command reads the composer.json file from the
@@ -55,11 +54,10 @@ EOT
             ->setDryRun($input->getOption('dry-run'))
             ->setVerbose($input->getOption('verbose'))
             ->setPreferSource($input->getOption('prefer-source'))
-            ->setInstallRecommends(!$input->getOption('no-install-recommends'))
-            ->setInstallSuggests($input->getOption('install-suggests'))
+            ->setDevMode($input->getOption('dev'))
             ->setUpdate(true)
         ;
 
-        return $install->run();
+        return $install->run() ? 0 : 1;
     }
 }

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

@@ -283,18 +283,6 @@ class Solver
                     }
                 }
             }
-
-            foreach ($package->getRecommends() as $link) {
-                foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $recommend) {
-                    $workQueue->enqueue($recommend);
-                }
-            }
-
-            foreach ($package->getSuggests() as $link) {
-                foreach ($this->pool->whatProvides($link->getTarget(), $link->getConstraint()) as $suggest) {
-                    $workQueue->enqueue($suggest);
-                }
-            }
         }
     }
 

+ 9 - 6
src/Composer/Factory.php

@@ -152,6 +152,7 @@ class Factory
     protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
     {
         $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json')));
+        $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed_dev.json')));
     }
 
     protected function addPackagistRepository(array $localConfig)
@@ -202,18 +203,20 @@ class Factory
     protected function createInstallationManager(Repository\RepositoryManager $rm, Downloader\DownloadManager $dm, $vendorDir, $binDir, IOInterface $io)
     {
         $im = new Installer\InstallationManager($vendorDir);
-        $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, null));
-        $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, $im));
-        $im->addInstaller(new Installer\MetapackageInstaller($rm->getLocalRepository(), $io));
+        $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $io, null));
+        $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $io, $im, $rm->getLocalRepositories()));
+        $im->addInstaller(new Installer\MetapackageInstaller($io));
 
         return $im;
     }
 
     protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im)
     {
-        foreach ($rm->getLocalRepository()->getPackages() as $package) {
-            if (!$im->isPackageInstalled($package)) {
-                $rm->getLocalRepository()->removePackage($package);
+        foreach ($rm->getLocalRepositories() as $repo) {
+            foreach ($repo->getPackages() as $package) {
+                if (!$im->isPackageInstalled($repo, $package)) {
+                    $repo->removePackage($package);
+                }
             }
         }
     }

+ 114 - 90
src/Composer/Installer.php

@@ -77,10 +77,9 @@ class Installer
     protected $eventDispatcher;
 
     protected $preferSource = false;
+    protected $devMode = false;
     protected $dryRun = false;
     protected $verbose = false;
-    protected $installRecommends = true;
-    protected $installSuggests = false;
     protected $update = false;
 
     /**
@@ -123,33 +122,14 @@ class Installer
             $this->downloadManager->setPreferSource(true);
         }
 
-        // create local repo, this contains all packages that are installed in the local project
-        $localRepo = $this->repositoryManager->getLocalRepository();
         // create installed repo, this contains all local packages + platform packages (php & extensions)
-        $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository()));
+        $repos = array_merge($this->repositoryManager->getLocalRepositories(), array(new PlatformRepository()));
+        $installedRepo = new CompositeRepository($repos);
         if ($this->additionalInstalledRepository) {
             $installedRepo->addRepository($this->additionalInstalledRepository);
         }
 
-        // prepare aliased packages
-        if (!$this->update && $this->locker->isLocked()) {
-            $aliases = $this->locker->getAliases();
-        } else {
-            $aliases = $this->package->getAliases();
-        }
-        foreach ($aliases as $alias) {
-            foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) {
-                $package->setAlias($alias['alias_normalized']);
-                $package->setPrettyAlias($alias['alias']);
-                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-            }
-            foreach ($this->repositoryManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) {
-                $package->setAlias($alias['alias_normalized']);
-                $package->setPrettyAlias($alias['alias']);
-                $this->repositoryManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-                $this->repositoryManager->getLocalRepository()->removePackage($package);
-            }
-        }
+        $aliases = $this->aliasPackages();
 
         // creating repository pool
         $pool = new Pool;
@@ -158,34 +138,74 @@ class Installer
             $pool->addRepository($repository);
         }
 
-        // dispatch pre event
         if (!$this->dryRun) {
+            // dispatch pre event
             $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
             $this->eventDispatcher->dispatchCommandEvent($eventName);
         }
 
+        $suggestedPackages = $this->doInstall($this->repositoryManager->getLocalRepository(), $installedRepo, $pool, $aliases);
+        if ($this->devMode) {
+            $devSuggested = $this->doInstall($this->repositoryManager->getLocalDevRepository(), $installedRepo, $pool, $aliases, true);
+            $suggestedPackages = array_merge($suggestedPackages, $devSuggested);
+        }
+
+        // dump suggestions
+        foreach ($suggestedPackages as $suggestion) {
+            $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
+        }
+
+        if (!$this->dryRun) {
+            // write lock
+            if ($this->update || !$this->locker->isLocked()) {
+                $updatedLock = $this->locker->setLockData(
+                    $this->repositoryManager->getLocalRepository()->getPackages(),
+                    $this->repositoryManager->getLocalDevRepository()->getPackages(),
+                    $aliases
+                );
+                if ($updatedLock) {
+                    $this->io->write('<info>Writing lock file</info>');
+                }
+            }
+
+            // write autoloader
+            $this->io->write('<info>Generating autoload files</info>');
+            $generator = new AutoloadGenerator;
+            $localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories());
+            $generator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer');
+
+            // dispatch post event
+            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
+            $this->eventDispatcher->dispatchCommandEvent($eventName);
+        }
+
+        return true;
+    }
+
+    protected function doInstall($localRepo, $installedRepo, $pool, $aliases, $devMode = false)
+    {
         // creating requirements request
         $installFromLock = false;
         $request = new Request($pool);
         if ($this->update) {
-            $this->io->write('Updating dependencies');
+            $this->io->write('<info>Updating '.($devMode ? 'dev ': '').'dependencies</info>');
 
             $request->updateAll();
 
-            $links = $this->collectLinks();
+            $links = $devMode ? $this->package->getDevRequires() : $this->package->getRequires();
 
             foreach ($links as $link) {
                 $request->install($link->getTarget(), $link->getConstraint());
             }
         } elseif ($this->locker->isLocked()) {
             $installFromLock = true;
-            $this->io->write('Installing from lock file');
+            $this->io->write('<info>Installing '.($devMode ? 'dev ': '').'dependencies from lock file</info>');
 
-            if (!$this->locker->isFresh()) {
+            if (!$this->locker->isFresh() && !$devMode) {
                 $this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
             }
 
-            foreach ($this->locker->getLockedPackages() as $package) {
+            foreach ($this->locker->getLockedPackages($devMode) as $package) {
                 $version = $package->getVersion();
                 foreach ($aliases as $alias) {
                     if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
@@ -197,15 +217,25 @@ class Installer
                 $request->install($package->getName(), $constraint);
             }
         } else {
-            $this->io->write('Installing dependencies');
+            $this->io->write('<info>Installing '.($devMode ? 'dev ': '').'dependencies</info>');
 
-            $links = $this->collectLinks();
+            $links = $devMode ? $this->package->getDevRequires() : $this->package->getRequires();
 
             foreach ($links as $link) {
                 $request->install($link->getTarget(), $link->getConstraint());
             }
         }
 
+        // fix the version all installed packages that are not in the current local repo to prevent rogue updates
+        foreach ($installedRepo->getPackages() as $package) {
+            if ($package->getRepository() === $localRepo || $package->getRepository() instanceof PlatformRepository) {
+                continue;
+            }
+
+            $constraint = new VersionConstraint('=', $package->getVersion());
+            $request->install($package->getName(), $constraint);
+        }
+
         // prepare solver
         $policy = new DefaultPolicy();
         $solver = new Solver($policy, $pool, $installedRepo);
@@ -262,22 +292,35 @@ class Installer
         }
 
         // anti-alias local repository to allow updates to work fine
-        foreach ($this->repositoryManager->getLocalRepository()->getPackages() as $package) {
+        foreach ($localRepo->getPackages() as $package) {
             if ($package instanceof AliasPackage) {
-                $this->repositoryManager->getLocalRepository()->addPackage(clone $package->getAliasOf());
-                $this->repositoryManager->getLocalRepository()->removePackage($package);
+                $package->getRepository()->addPackage(clone $package->getAliasOf());
+                $package->getRepository()->removePackage($package);
             }
         }
 
         // execute operations
         if (!$operations) {
-            $this->io->write('<info>Nothing to install or update</info>');
+            $this->io->write('Nothing to install or update');
         }
 
+        $suggestedPackages = array();
         foreach ($operations as $operation) {
             if ($this->verbose) {
                 $this->io->write((string) $operation);
             }
+
+            // collect suggestions
+            if ('install' === $operation->getJobType()) {
+                foreach ($operation->getPackage()->getSuggests() as $target => $reason) {
+                    $suggestedPackages[] = array(
+                        'source' => $operation->getPackage()->getPrettyName(),
+                        'target' => $target,
+                        'reason' => $reason,
+                    );
+                }
+            }
+
             if (!$this->dryRun) {
                 $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
 
@@ -299,7 +342,7 @@ class Installer
                         }
                     }
                 }
-                $this->installationManager->execute($operation);
+                $this->installationManager->execute($localRepo, $operation);
 
                 $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
 
@@ -307,40 +350,34 @@ class Installer
             }
         }
 
-        if (!$this->dryRun) {
-            if ($this->update || !$this->locker->isLocked()) {
-                if ($this->locker->setLockData($localRepo->getPackages(), $aliases)) {
-                    $this->io->write('<info>Writing lock file</info>');
-                }
-            }
-
-            $localRepo->write();
-
-            $this->io->write('<info>Generating autoload files</info>');
-            $generator = new AutoloadGenerator;
-            $generator->dump($localRepo, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer');
-
-            // dispatch post event
-            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
-            $this->eventDispatcher->dispatchCommandEvent($eventName);
-        }
-
-        return true;
+        return $suggestedPackages;
     }
 
-    private function collectLinks()
+    private function aliasPackages()
     {
-        $links = $this->package->getRequires();
-
-        if ($this->installRecommends) {
-            $links = array_merge($links, $this->package->getRecommends());
+        if (!$this->update && $this->locker->isLocked()) {
+            $aliases = $this->locker->getAliases();
+        } else {
+            $aliases = $this->package->getAliases();
         }
 
-        if ($this->installSuggests) {
-            $links = array_merge($links, $this->package->getSuggests());
+        foreach ($aliases as $alias) {
+            foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) {
+                $package->setAlias($alias['alias_normalized']);
+                $package->setPrettyAlias($alias['alias']);
+                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+            }
+            foreach ($this->repositoryManager->getLocalRepositories() as $repo) {
+                foreach ($repo->findPackages($alias['package'], $alias['version']) as $package) {
+                    $package->setAlias($alias['alias_normalized']);
+                    $package->setPrettyAlias($alias['alias']);
+                    $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                    $package->getRepository()->removePackage($package);
+                }
+            }
         }
 
-        return $links;
+        return $aliases;
     }
 
     /**
@@ -379,7 +416,7 @@ class Installer
      * @param boolean $dryRun
      * @return Installer
      */
-    public function setDryRun($dryRun=true)
+    public function setDryRun($dryRun = true)
     {
         $this->dryRun = (boolean) $dryRun;
 
@@ -387,53 +424,40 @@ class Installer
     }
 
     /**
-     * install recommend packages
-     *
-     * @param boolean $noInstallRecommends
-     * @return Installer
-     */
-    public function setInstallRecommends($installRecommends=true)
-    {
-        $this->installRecommends = (boolean) $installRecommends;
-
-        return $this;
-    }
-
-    /**
-     * also install suggested packages
+     * prefer source installation
      *
-     * @param boolean $installSuggests
+     * @param boolean $preferSource
      * @return Installer
      */
-    public function setInstallSuggests($installSuggests=true)
+    public function setPreferSource($preferSource = true)
     {
-        $this->installSuggests = (boolean) $installSuggests;
+        $this->preferSource = (boolean) $preferSource;
 
         return $this;
     }
 
     /**
-     * prefer source installation
+     * update packages
      *
-     * @param boolean $preferSource
+     * @param boolean $update
      * @return Installer
      */
-    public function setPreferSource($preferSource=true)
+    public function setUpdate($update = true)
     {
-        $this->preferSource = (boolean) $preferSource;
+        $this->update = (boolean) $update;
 
         return $this;
     }
 
     /**
-     * update packages
+     * enables dev packages
      *
      * @param boolean $update
      * @return Installer
      */
-    public function setUpdate($update=true)
+    public function setDevMode($devMode = true)
     {
-        $this->update = (boolean) $update;
+        $this->devMode = (boolean) $devMode;
 
         return $this;
     }
@@ -444,7 +468,7 @@ class Installer
      * @param boolean $verbose
      * @return Installer
      */
-    public function setVerbose($verbose=true)
+    public function setVerbose($verbose = true)
     {
         $this->verbose = (boolean) $verbose;
 

+ 18 - 12
src/Composer/Installer/InstallationManager.php

@@ -14,6 +14,7 @@ namespace Composer\Installer;
 
 use Composer\Package\PackageInterface;
 use Composer\Package\AliasPackage;
+use Composer\Repository\RepositoryInterface;
 use Composer\Repository\NotifiableRepositoryInterface;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\DependencyResolver\Operation\InstallOperation;
@@ -95,32 +96,35 @@ class InstallationManager
     /**
      * Checks whether provided package is installed in one of the registered installers.
      *
+     * @param   RepositoryInterface    $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      *
      * @return  Boolean
      */
-    public function isPackageInstalled(PackageInterface $package)
+    public function isPackageInstalled(RepositoryInterface $repo, PackageInterface $package)
     {
-        return $this->getInstaller($package->getType())->isInstalled($package);
+        return $this->getInstaller($package->getType())->isInstalled($repo, $package);
     }
 
     /**
      * Executes solver operation.
      *
+     * @param   RepositoryInterface $repo       repository in which to check
      * @param   OperationInterface  $operation  operation instance
      */
-    public function execute(OperationInterface $operation)
+    public function execute(RepositoryInterface $repo, OperationInterface $operation)
     {
         $method = $operation->getJobType();
-        $this->$method($operation);
+        $this->$method($repo, $operation);
     }
 
     /**
      * Executes install operation.
      *
+     * @param   RepositoryInterface $repo       repository in which to check
      * @param   InstallOperation    $operation  operation instance
      */
-    public function install(InstallOperation $operation)
+    public function install(RepositoryInterface $repo, InstallOperation $operation)
     {
         $package = $operation->getPackage();
         if ($package instanceof AliasPackage) {
@@ -128,16 +132,17 @@ class InstallationManager
             $package->setInstalledAsAlias(true);
         }
         $installer = $this->getInstaller($package->getType());
-        $installer->install($package);
+        $installer->install($repo, $package);
         $this->notifyInstall($package);
     }
 
     /**
      * Executes update operation.
      *
+     * @param   RepositoryInterface $repo       repository in which to check
      * @param   InstallOperation    $operation  operation instance
      */
-    public function update(UpdateOperation $operation)
+    public function update(RepositoryInterface $repo, UpdateOperation $operation)
     {
         $initial = $operation->getInitialPackage();
         if ($initial instanceof AliasPackage) {
@@ -154,27 +159,28 @@ class InstallationManager
 
         if ($initialType === $targetType) {
             $installer = $this->getInstaller($initialType);
-            $installer->update($initial, $target);
+            $installer->update($repo, $initial, $target);
             $this->notifyInstall($target);
         } else {
-            $this->getInstaller($initialType)->uninstall($initial);
-            $this->getInstaller($targetType)->install($target);
+            $this->getInstaller($initialType)->uninstall($repo, $initial);
+            $this->getInstaller($targetType)->install($repo, $target);
         }
     }
 
     /**
      * Uninstalls package.
      *
+     * @param   RepositoryInterface $repo       repository in which to check
      * @param   UninstallOperation  $operation  operation instance
      */
-    public function uninstall(UninstallOperation $operation)
+    public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
     {
         $package = $operation->getPackage();
         if ($package instanceof AliasPackage) {
             $package = $package->getAliasOf();
         }
         $installer = $this->getInstaller($package->getType());
-        $installer->uninstall($package);
+        $installer->uninstall($repo, $package);
     }
 
     /**

+ 14 - 11
src/Composer/Installer/InstallerInstaller.php

@@ -33,17 +33,20 @@ class InstallerInstaller extends LibraryInstaller
      * @param   string                      $vendorDir  relative path for packages home
      * @param   string                      $binDir     relative path for binaries
      * @param   DownloadManager             $dm         download manager
-     * @param   WritableRepositoryInterface $repository repository controller
      * @param   IOInterface                 $io         io instance
+     * @param   InstallationManager         $im         installation manager
+     * @param   array                       $localRepositories array of WritableRepositoryInterface
      */
-    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, IOInterface $io, InstallationManager $im)
+    public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, InstallationManager $im, array $localRepositories)
     {
-        parent::__construct($vendorDir, $binDir, $dm, $repository, $io, 'composer-installer');
+        parent::__construct($vendorDir, $binDir, $dm, $io, 'composer-installer');
         $this->installationManager = $im;
 
-        foreach ($repository->getPackages() as $package) {
-            if ('composer-installer' === $package->getType()) {
-                $this->registerInstaller($package);
+        foreach ($localRepositories as $repo) {
+            foreach ($repo->getPackages() as $package) {
+                if ('composer-installer' === $package->getType()) {
+                    $this->registerInstaller($package);
+                }
             }
         }
     }
@@ -51,28 +54,28 @@ class InstallerInstaller extends LibraryInstaller
     /**
      * {@inheritDoc}
      */
-    public function install(PackageInterface $package)
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
     {
         $extra = $package->getExtra();
         if (empty($extra['class'])) {
             throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.');
         }
 
-        parent::install($package);
+        parent::install($repo, $package);
         $this->registerInstaller($package);
     }
 
     /**
      * {@inheritDoc}
      */
-    public function update(PackageInterface $initial, PackageInterface $target)
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
         $extra = $target->getExtra();
         if (empty($extra['class'])) {
             throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-installer packages should have a class defined in their extra key to be usable.');
         }
 
-        parent::update($initial, $target);
+        parent::update($repo, $initial, $target);
         $this->registerInstaller($target);
     }
 
@@ -97,7 +100,7 @@ class InstallerInstaller extends LibraryInstaller
         }
 
         $extra = $package->getExtra();
-        $installer = new $class($this->vendorDir, $this->binDir, $this->downloadManager, $this->repository, $this->io);
+        $installer = new $class($this->vendorDir, $this->binDir, $this->downloadManager, $this->io);
         $this->installationManager->addInstaller($installer);
     }
 }

+ 10 - 4
src/Composer/Installer/InstallerInterface.php

@@ -14,11 +14,13 @@ namespace Composer\Installer;
 
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Package\PackageInterface;
+use Composer\Repository\WritableRepositoryInterface;
 
 /**
  * Interface for the package installation manager.
  *
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 interface InstallerInterface
 {
@@ -33,35 +35,39 @@ interface InstallerInterface
     /**
      * Checks that provided package is installed.
      *
+     * @param   WritableRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      *
      * @return  Boolean
      */
-    function isInstalled(PackageInterface $package);
+    function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Installs specific package.
      *
+     * @param   WritableRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      */
-    function install(PackageInterface $package);
+    function install(WritableRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Updates specific package.
      *
+     * @param   WritableRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $initial    already installed package version
      * @param   PackageInterface    $target     updated version
      *
      * @throws  InvalidArgumentException        if $from package is not installed
      */
-    function update(PackageInterface $initial, PackageInterface $target);
+    function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);
 
     /**
      * Uninstalls specific package.
      *
+     * @param   WritableRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      */
-    function uninstall(PackageInterface $package);
+    function uninstall(WritableRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Returns the installation path of a package

+ 16 - 18
src/Composer/Installer/LibraryInstaller.php

@@ -30,7 +30,6 @@ class LibraryInstaller implements InstallerInterface
     protected $vendorDir;
     protected $binDir;
     protected $downloadManager;
-    protected $repository;
     protected $io;
     private $type;
     private $filesystem;
@@ -41,14 +40,12 @@ class LibraryInstaller implements InstallerInterface
      * @param   string                      $vendorDir  relative path for packages home
      * @param   string                      $binDir     relative path for binaries
      * @param   DownloadManager             $dm         download manager
-     * @param   WritableRepositoryInterface $repository repository controller
      * @param   IOInterface                 $io         io instance
      * @param   string                      $type       package type that this installer handles
      */
-    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, IOInterface $io, $type = 'library')
+    public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, $type = 'library')
     {
         $this->downloadManager = $dm;
-        $this->repository = $repository;
         $this->io = $io;
         $this->type = $type;
 
@@ -68,37 +65,37 @@ class LibraryInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function isInstalled(PackageInterface $package)
+    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package)
     {
-        return $this->repository->hasPackage($package) && is_readable($this->getInstallPath($package));
+        return $repo->hasPackage($package) && is_readable($this->getInstallPath($package));
     }
 
     /**
      * {@inheritDoc}
      */
-    public function install(PackageInterface $package)
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
     {
         $this->initializeVendorDir();
         $downloadPath = $this->getInstallPath($package);
 
         // remove the binaries if it appears the package files are missing
-        if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
+        if (!is_readable($downloadPath) && $repo->hasPackage($package)) {
             $this->removeBinaries($package);
         }
 
         $this->downloadManager->download($package, $downloadPath);
         $this->installBinaries($package);
-        if (!$this->repository->hasPackage($package)) {
-            $this->repository->addPackage(clone $package);
+        if (!$repo->hasPackage($package)) {
+            $repo->addPackage(clone $package);
         }
     }
 
     /**
      * {@inheritDoc}
      */
-    public function update(PackageInterface $initial, PackageInterface $target)
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
-        if (!$this->repository->hasPackage($initial)) {
+        if (!$repo->hasPackage($initial)) {
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
         }
 
@@ -108,18 +105,18 @@ class LibraryInstaller implements InstallerInterface
         $this->removeBinaries($initial);
         $this->downloadManager->update($initial, $target, $downloadPath);
         $this->installBinaries($target);
-        $this->repository->removePackage($initial);
-        if (!$this->repository->hasPackage($target)) {
-            $this->repository->addPackage(clone $target);
+        $repo->removePackage($initial);
+        if (!$repo->hasPackage($target)) {
+            $repo->addPackage(clone $target);
         }
     }
 
     /**
      * {@inheritDoc}
      */
-    public function uninstall(PackageInterface $package)
+    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package)
     {
-        if (!$this->repository->hasPackage($package)) {
+        if (!$repo->hasPackage($package)) {
             // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)
             return;
             throw new \InvalidArgumentException('Package is not installed: '.$package);
@@ -129,7 +126,7 @@ class LibraryInstaller implements InstallerInterface
 
         $this->downloadManager->remove($package, $downloadPath);
         $this->removeBinaries($package);
-        $this->repository->removePackage($package);
+        $repo->removePackage($package);
     }
 
     /**
@@ -138,6 +135,7 @@ class LibraryInstaller implements InstallerInterface
     public function getInstallPath(PackageInterface $package)
     {
         $targetDir = $package->getTargetDir();
+
         return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
     }
 

+ 11 - 25
src/Composer/Installer/MetapackageInstaller.php

@@ -12,7 +12,6 @@
 
 namespace Composer\Installer;
 
-use Composer\IO\IOInterface;
 use Composer\Repository\WritableRepositoryInterface;
 use Composer\Package\PackageInterface;
 
@@ -23,19 +22,6 @@ use Composer\Package\PackageInterface;
  */
 class MetapackageInstaller implements InstallerInterface
 {
-    protected $repository;
-    protected $io;
-
-    /**
-     * @param   WritableRepositoryInterface $repository repository controller
-     * @param   IOInterface                 $io         io instance
-     */
-    public function __construct(WritableRepositoryInterface $repository, IOInterface $io)
-    {
-        $this->repository = $repository;
-        $this->io = $io;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -47,44 +33,44 @@ class MetapackageInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function isInstalled(PackageInterface $package)
+    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package)
     {
-        return $this->repository->hasPackage($package);
+        return $repo->hasPackage($package);
     }
 
     /**
      * {@inheritDoc}
      */
-    public function install(PackageInterface $package)
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
     {
-        $this->repository->addPackage(clone $package);
+        $repo->addPackage(clone $package);
     }
 
     /**
      * {@inheritDoc}
      */
-    public function update(PackageInterface $initial, PackageInterface $target)
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
-        if (!$this->repository->hasPackage($initial)) {
+        if (!$repo->hasPackage($initial)) {
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
         }
 
-        $this->repository->removePackage($initial);
-        $this->repository->addPackage(clone $target);
+        $repo->removePackage($initial);
+        $repo->addPackage(clone $target);
     }
 
     /**
      * {@inheritDoc}
      */
-    public function uninstall(PackageInterface $package)
+    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package)
     {
-        if (!$this->repository->hasPackage($package)) {
+        if (!$repo->hasPackage($package)) {
             // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)
             return;
             throw new \InvalidArgumentException('Package is not installed: '.$package);
         }
 
-        $this->repository->removePackage($package);
+        $repo->removePackage($package);
     }
 
     /**

+ 7 - 11
src/Composer/Package/AliasPackage.php

@@ -52,7 +52,7 @@ class AliasPackage extends BasePackage
         $this->dev = VersionParser::isDev($version);
 
         // replace self.version dependencies
-        foreach (array('requires', 'recommends', 'suggests') as $type) {
+        foreach (array('requires', 'devRequires') as $type) {
             $links = $aliasOf->{'get'.ucfirst($type)}();
             foreach ($links as $index => $link) {
                 // link is self.version, but must be replacing also the replaced version
@@ -141,17 +141,9 @@ class AliasPackage extends BasePackage
     /**
      * {@inheritDoc}
      */
-    public function getRecommends()
+    public function getDevRequires()
     {
-        return $this->recommends;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getSuggests()
-    {
-        return $this->suggests;
+        return $this->devRequires;
     }
 
     /**
@@ -266,6 +258,10 @@ class AliasPackage extends BasePackage
     {
         return $this->aliasOf->getHomepage();
     }
+    public function getSuggests()
+    {
+        return $this->aliasOf->getSuggests();
+    }
     public function getAuthors()
     {
         return $this->aliasOf->getAuthors();

+ 5 - 6
src/Composer/Package/BasePackage.php

@@ -25,12 +25,11 @@ use Composer\Repository\PlatformRepository;
 abstract class BasePackage implements PackageInterface
 {
     public static $supportedLinkTypes = array(
-        'require'   => 'requires',
-        'conflict'  => 'conflicts',
-        'provide'   => 'provides',
-        'replace'   => 'replaces',
-        'recommend' => 'recommends',
-        'suggest'   => 'suggests',
+        'require'   => array('description' => 'requires', 'method' => 'requires'),
+        'conflict'  => array('description' => 'conflicts', 'method' => 'conflicts'),
+        'provide'   => array('description' => 'provides', 'method' => 'provides'),
+        'replace'   => array('description' => 'replaces', 'method' => 'replaces'),
+        'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'),
     );
 
     protected $name;

+ 9 - 4
src/Composer/Package/Dumper/ArrayDumper.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Package\Dumper;
 
+use Composer\Package\BasePackage;
 use Composer\Package\PackageInterface;
 
 /**
@@ -26,7 +27,6 @@ class ArrayDumper
             'binaries' => 'bin',
             'scripts',
             'type',
-            'names',
             'extra',
             'installationSource' => 'installation-source',
             'license',
@@ -36,6 +36,7 @@ class ArrayDumper
             'keywords',
             'autoload',
             'repositories',
+            'includePaths' => 'include-path',
         );
 
         $data = array();
@@ -64,14 +65,18 @@ class ArrayDumper
             $data['dist']['shasum'] = $package->getDistSha1Checksum();
         }
 
-        foreach (array('require', 'conflict', 'provide', 'replace', 'suggest', 'recommend') as $linkType) {
-            if ($links = $package->{'get'.ucfirst($linkType).'s'}()) {
+        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
+            if ($links = $package->{'get'.ucfirst($opts['method'])}()) {
                 foreach ($links as $link) {
-                    $data[$linkType][$link->getTarget()] = $link->getPrettyConstraint();
+                    $data[$type][$link->getTarget()] = $link->getPrettyConstraint();
                 }
             }
         }
 
+        if ($packages = $package->getSuggests()) {
+            $data['suggest'] = $packages;
+        }
+
         foreach ($keys as $method => $key) {
             if (is_numeric($method)) {
                 $method = $key;

+ 7 - 3
src/Composer/Package/Loader/ArrayLoader.php

@@ -156,15 +156,19 @@ class ArrayLoader
             }
         }
 
-        foreach (Package\BasePackage::$supportedLinkTypes as $type => $description) {
+        foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
             if (isset($config[$type])) {
-                $method = 'set'.ucfirst($description);
+                $method = 'set'.ucfirst($opts['method']);
                 $package->{$method}(
-                    $this->loadLinksFromConfig($package, $description, $config[$type])
+                    $this->loadLinksFromConfig($package, $opts['description'], $config[$type])
                 );
             }
         }
 
+        if (isset($config['suggest']) && is_array($config['suggest'])) {
+            $package->setSuggests($config['suggest']);
+        }
+
         if (isset($config['autoload'])) {
             $package->setAutoload($config['autoload']);
         }

+ 30 - 14
src/Composer/Package/Locker.php

@@ -69,13 +69,17 @@ class Locker
      *
      * @return array
      */
-    public function getLockedPackages()
+    public function getLockedPackages($dev = false)
     {
         $lockList = $this->getLockData();
         $packages = array();
-        foreach ($lockList['packages'] as $info) {
+
+        $lockedPackages = $dev ? $lockList['packages-dev'] : $lockList['packages'];
+        $repo = $dev ? $this->repositoryManager->getLocalDevRepository() : $this->repositoryManager->getLocalRepository();
+
+        foreach ($lockedPackages as $info) {
             $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version'];
-            $package = $this->repositoryManager->getLocalRepository()->findPackage($info['package'], $resolvedVersion);
+            $package = $repo->findPackage($info['package'], $resolvedVersion);
 
             if (!$package) {
                 $package = $this->repositoryManager->findPackage($info['package'], $info['version']);
@@ -120,18 +124,37 @@ class Locker
      * Locks provided data into lockfile.
      *
      * @param array $packages array of packages
+     * @param array $packages array of dev packages
      * @param array $aliases array of aliases
      *
      * @return Boolean
      */
-    public function setLockData(array $packages, array $aliases)
+    public function setLockData(array $packages, array $devPackages, array $aliases)
     {
         $lock = array(
             'hash' => $this->hash,
             'packages' => array(),
+            'packages-dev' => array(),
             'aliases' => $aliases,
         );
 
+        $lock['packages'] = $this->lockPackages($packages);
+        $lock['packages-dev'] = $this->lockPackages($devPackages);
+
+        if (!$this->isLocked() || $lock !== $this->getLockData()) {
+            $this->lockFile->write($lock);
+            $this->lockDataCache = null;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private function lockPackages(array $packages)
+    {
+        $locked = array();
+
         foreach ($packages as $package) {
             $name    = $package->getPrettyName();
             $version = $package->getPrettyVersion();
@@ -152,20 +175,13 @@ class Locker
                 $spec['alias'] = $package->getAlias();
             }
 
-            $lock['packages'][] = $spec;
+            $locked[] = $spec;
         }
 
-        usort($lock['packages'], function ($a, $b) {
+        usort($locked, function ($a, $b) {
             return strcmp($a['package'], $b['package']);
         });
 
-        if (!$this->isLocked() || $lock !== $this->getLockData()) {
-            $this->lockFile->write($lock);
-            $this->lockDataCache = null;
-
-            return true;
-        }
-
-        return false;
+        return $locked;
     }
 }

+ 7 - 7
src/Composer/Package/MemoryPackage.php

@@ -53,7 +53,7 @@ class MemoryPackage extends BasePackage
     protected $conflicts = array();
     protected $provides = array();
     protected $replaces = array();
-    protected $recommends = array();
+    protected $devRequires = array();
     protected $suggests = array();
     protected $autoload = array();
     protected $includePaths = array();
@@ -484,25 +484,25 @@ class MemoryPackage extends BasePackage
     /**
      * Set the recommended packages
      *
-     * @param array $recommends A set of package links
+     * @param array $devRequires A set of package links
      */
-    public function setRecommends(array $recommends)
+    public function setDevRequires(array $devRequires)
     {
-        $this->recommends = $recommends;
+        $this->devRequires = $devRequires;
     }
 
     /**
      * {@inheritDoc}
      */
-    public function getRecommends()
+    public function getDevRequires()
     {
-        return $this->recommends;
+        return $this->devRequires;
     }
 
     /**
      * Set the suggested packages
      *
-     * @param array $suggests A set of package links
+     * @param array $suggests A set of package names/comments
      */
     public function setSuggests(array $suggests)
     {

+ 7 - 9
src/Composer/Package/PackageInterface.php

@@ -220,20 +220,18 @@ interface PackageInterface
     function getReplaces();
 
     /**
-     * Returns a set of links to packages which are recommended in
-     * combination with this package. These would most likely be installed
-     * automatically in combination with this package.
+     * Returns a set of links to packages which are required to develop
+     * this package. These are installed if in dev mode.
      *
-     * @return array An array of package links defining recommended packages
+     * @return array An array of package links defining packages required for development
      */
-    function getRecommends();
+    function getDevRequires();
 
     /**
-     * Returns a set of links to packages which are suggested in combination
-     * with this package. These can be suggested to the user, but will not be
-     * automatically installed with this package.
+     * Returns a set of package names and reasons why they are useful in
+     * combination with this package.
      *
-     * @return array An array of package links defining suggested packages
+     * @return array An array of package suggestions with descriptions
      */
     function getSuggests();
 

+ 31 - 0
src/Composer/Repository/RepositoryManager.php

@@ -25,6 +25,7 @@ use Composer\Config;
 class RepositoryManager
 {
     private $localRepository;
+    private $localDevRepository;
     private $repositories = array();
     private $repositoryClasses = array();
     private $io;
@@ -140,4 +141,34 @@ class RepositoryManager
     {
         return $this->localRepository;
     }
+
+    /**
+     * Sets localDev repository for the project.
+     *
+     * @param   RepositoryInterface $repository repository instance
+     */
+    public function setLocalDevRepository(RepositoryInterface $repository)
+    {
+        $this->localDevRepository = $repository;
+    }
+
+    /**
+     * Returns localDev repository for the project.
+     *
+     * @return  RepositoryInterface
+     */
+    public function getLocalDevRepository()
+    {
+        return $this->localDevRepository;
+    }
+
+    /**
+     * Returns all local repositories for the project.
+     *
+     * @return  array[RepositoryInterface]
+     */
+    public function getLocalRepositories()
+    {
+        return array($this->localRepository, $this->localDevRepository);
+    }
 }

+ 5 - 4
tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php

@@ -4,15 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
+use Composer\Repository\WritableRepositoryInterface;
 
 class Custom implements InstallerInterface
 {
     public $version = 'installer-v1';
 
     public function supports($packageType) {}
-    public function isInstalled(PackageInterface $package) {}
-    public function install(PackageInterface $package) {}
-    public function update(PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(PackageInterface $package) {}
+    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 5 - 4
tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php

@@ -4,15 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
+use Composer\Repository\WritableRepositoryInterface;
 
 class Custom2 implements InstallerInterface
 {
     public $version = 'installer-v2';
 
     public function supports($packageType) {}
-    public function isInstalled(PackageInterface $package) {}
-    public function install(PackageInterface $package) {}
-    public function update(PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(PackageInterface $package) {}
+    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 5 - 4
tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php

@@ -4,15 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
+use Composer\Repository\WritableRepositoryInterface;
 
 class Custom2 implements InstallerInterface
 {
     public $version = 'installer-v3';
 
     public function supports($packageType) {}
-    public function isInstalled(PackageInterface $package) {}
-    public function install(PackageInterface $package) {}
-    public function update(PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(PackageInterface $package) {}
+    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 20 - 15
tests/Composer/Test/Installer/InstallationManagerTest.php

@@ -19,6 +19,11 @@ use Composer\DependencyResolver\Operation\UninstallOperation;
 
 class InstallationManagerTest extends \PHPUnit_Framework_TestCase
 {
+    public function setUp()
+    {
+        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
+    }
+
     public function testVendorDirOutsideTheWorkingDir()
     {
         $manager = new InstallationManager(realpath(getcwd().'/../'));
@@ -70,19 +75,19 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
         $manager
             ->expects($this->once())
             ->method('install')
-            ->with($installOperation);
+            ->with($this->repository, $installOperation);
         $manager
             ->expects($this->once())
             ->method('uninstall')
-            ->with($removeOperation);
+            ->with($this->repository, $removeOperation);
         $manager
             ->expects($this->once())
             ->method('update')
-            ->with($updateOperation);
+            ->with($this->repository, $updateOperation);
 
-        $manager->execute($installOperation);
-        $manager->execute($removeOperation);
-        $manager->execute($updateOperation);
+        $manager->execute($this->repository, $installOperation);
+        $manager->execute($this->repository, $removeOperation);
+        $manager->execute($this->repository, $updateOperation);
     }
 
     public function testInstall()
@@ -108,9 +113,9 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
         $installer
             ->expects($this->once())
             ->method('install')
-            ->with($package);
+            ->with($this->repository, $package);
 
-        $manager->install($operation);
+        $manager->install($this->repository, $operation);
     }
 
     public function testUpdateWithEqualTypes()
@@ -141,9 +146,9 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
         $installer
             ->expects($this->once())
             ->method('update')
-            ->with($initial, $target);
+            ->with($this->repository, $initial, $target);
 
-        $manager->update($operation);
+        $manager->update($this->repository, $operation);
     }
 
     public function testUpdateWithNotEqualTypes()
@@ -183,14 +188,14 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
         $libInstaller
             ->expects($this->once())
             ->method('uninstall')
-            ->with($initial);
+            ->with($this->repository, $initial);
 
         $bundleInstaller
             ->expects($this->once())
             ->method('install')
-            ->with($target);
+            ->with($this->repository, $target);
 
-        $manager->update($operation);
+        $manager->update($this->repository, $operation);
     }
 
     public function testUninstall()
@@ -210,7 +215,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
         $installer
             ->expects($this->once())
             ->method('uninstall')
-            ->with($package);
+            ->with($this->repository, $package);
 
         $installer
             ->expects($this->once())
@@ -218,7 +223,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
             ->with('library')
             ->will($this->returnValue(true));
 
-        $manager->uninstall($operation);
+        $manager->uninstall($this->repository, $operation);
     }
 
     public function testGetVendorPathAbsolute()

+ 8 - 10
tests/Composer/Test/Installer/InstallerInstallerTest.php

@@ -34,11 +34,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $this->repository = $this->getMockBuilder('Composer\Repository\WritableRepositoryInterface')
-            ->getMock();
+        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
 
-        $this->io = $this->getMockBuilder('Composer\IO\IOInterface')
-            ->getMock();
+        $this->io = $this->getMock('Composer\IO\IOInterface');
     }
 
     public function testInstallNewInstaller()
@@ -47,7 +45,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->expects($this->once())
             ->method('getPackages')
             ->will($this->returnValue(array()));
-        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
+        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->io, $this->im, array($this->repository));
 
         $test = $this;
         $this->im
@@ -57,7 +55,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
                 $test->assertEquals('installer-v1', $installer->version);
             }));
 
-        $installer->install($this->packages[0]);
+        $installer->install($this->repository, $this->packages[0]);
     }
 
     public function testUpgradeWithNewClassName()
@@ -70,7 +68,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->expects($this->exactly(2))
             ->method('hasPackage')
             ->will($this->onConsecutiveCalls(true, false));
-        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
+        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->io, $this->im, array($this->repository));
 
         $test = $this;
         $this->im
@@ -80,7 +78,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
                 $test->assertEquals('installer-v2', $installer->version);
             }));
 
-        $installer->update($this->packages[0], $this->packages[1]);
+        $installer->update($this->repository, $this->packages[0], $this->packages[1]);
     }
 
     public function testUpgradeWithSameClassName()
@@ -93,7 +91,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->expects($this->exactly(2))
             ->method('hasPackage')
             ->will($this->onConsecutiveCalls(true, false));
-        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
+        $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->io, $this->im, array($this->repository));
 
         $test = $this;
         $this->im
@@ -103,7 +101,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
                 $test->assertEquals('installer-v3', $installer->version);
             }));
 
-        $installer->update($this->packages[1], $this->packages[2]);
+        $installer->update($this->repository, $this->packages[1], $this->packages[2]);
     }
 }
 

+ 17 - 19
tests/Composer/Test/Installer/LibraryInstallerTest.php

@@ -40,11 +40,9 @@ class LibraryInstallerTest extends TestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $this->repository = $this->getMockBuilder('Composer\Repository\WritableRepositoryInterface')
-            ->getMock();
+        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
 
-        $this->io = $this->getMockBuilder('Composer\IO\IOInterface')
-            ->getMock();
+        $this->io = $this->getMock('Composer\IO\IOInterface');
     }
 
     protected function tearDown()
@@ -57,7 +55,7 @@ class LibraryInstallerTest extends TestCase
     {
         $this->fs->removeDirectory($this->vendorDir);
 
-        new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $this->assertFileNotExists($this->vendorDir);
     }
 
@@ -65,13 +63,13 @@ class LibraryInstallerTest extends TestCase
     {
         $this->fs->removeDirectory($this->binDir);
 
-        new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $this->assertFileNotExists($this->binDir);
     }
 
     public function testIsInstalled()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $package = $this->createPackageMock();
 
         $this->repository
@@ -80,8 +78,8 @@ class LibraryInstallerTest extends TestCase
             ->with($package)
             ->will($this->onConsecutiveCalls(true, false));
 
-        $this->assertTrue($library->isInstalled($package));
-        $this->assertFalse($library->isInstalled($package));
+        $this->assertTrue($library->isInstalled($this->repository, $package));
+        $this->assertFalse($library->isInstalled($this->repository, $package));
     }
 
     /**
@@ -90,7 +88,7 @@ class LibraryInstallerTest extends TestCase
      */
     public function testInstall()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $package = $this->createPackageMock();
 
         $package
@@ -108,7 +106,7 @@ class LibraryInstallerTest extends TestCase
             ->method('addPackage')
             ->with($package);
 
-        $library->install($package);
+        $library->install($this->repository, $package);
         $this->assertFileExists($this->vendorDir, 'Vendor dir should be created');
         $this->assertFileExists($this->binDir, 'Bin dir should be created');
     }
@@ -119,7 +117,7 @@ class LibraryInstallerTest extends TestCase
      */
     public function testUpdate()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $initial = $this->createPackageMock();
         $target  = $this->createPackageMock();
 
@@ -148,18 +146,18 @@ class LibraryInstallerTest extends TestCase
             ->method('addPackage')
             ->with($target);
 
-        $library->update($initial, $target);
+        $library->update($this->repository, $initial, $target);
         $this->assertFileExists($this->vendorDir, 'Vendor dir should be created');
         $this->assertFileExists($this->binDir, 'Bin dir should be created');
 
         $this->setExpectedException('InvalidArgumentException');
 
-        $library->update($initial, $target);
+        $library->update($this->repository, $initial, $target);
     }
 
     public function testUninstall()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $package = $this->createPackageMock();
 
         $package
@@ -183,17 +181,17 @@ class LibraryInstallerTest extends TestCase
             ->method('removePackage')
             ->with($package);
 
-        $library->uninstall($package);
+        $library->uninstall($this->repository, $package);
 
         // TODO re-enable once #125 is fixed and we throw exceptions again
 //        $this->setExpectedException('InvalidArgumentException');
 
-        $library->uninstall($package);
+        $library->uninstall($this->repository, $package);
     }
 
     public function testGetInstallPath()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $package = $this->createPackageMock();
 
         $package
@@ -206,7 +204,7 @@ class LibraryInstallerTest extends TestCase
 
     public function testGetInstallPathWithTargetDir()
     {
-        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io);
+        $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->io);
         $package = $this->createPackageMock();
 
         $package

+ 6 - 6
tests/Composer/Test/Installer/MetapackageInstallerTest.php

@@ -26,7 +26,7 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
 
         $this->io = $this->getMock('Composer\IO\IOInterface');
 
-        $this->installer = new MetapackageInstaller($this->repository, $this->io);
+        $this->installer = new MetapackageInstaller();
     }
 
     public function testInstall()
@@ -38,7 +38,7 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
             ->method('addPackage')
             ->with($package);
 
-        $this->installer->install($package);
+        $this->installer->install($this->repository, $package);
     }
 
     public function testUpdate()
@@ -62,11 +62,11 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
             ->method('addPackage')
             ->with($target);
 
-        $this->installer->update($initial, $target);
+        $this->installer->update($this->repository, $initial, $target);
 
         $this->setExpectedException('InvalidArgumentException');
 
-        $this->installer->update($initial, $target);
+        $this->installer->update($this->repository, $initial, $target);
     }
 
     public function testUninstall()
@@ -84,12 +84,12 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
             ->method('removePackage')
             ->with($package);
 
-        $this->installer->uninstall($package);
+        $this->installer->uninstall($this->repository, $package);
 
         // TODO re-enable once #125 is fixed and we throw exceptions again
 //        $this->setExpectedException('InvalidArgumentException');
 
-        $this->installer->uninstall($package);
+        $this->installer->uninstall($this->repository, $package);
     }
 
     private function createPackageMock()

+ 68 - 13
tests/Composer/Test/Package/Dumper/ArrayDumperTest.php

@@ -14,6 +14,8 @@ namespace Composer\Test\Package\Dumper;
 
 use Composer\Package\Dumper\ArrayDumper;
 use Composer\Package\MemoryPackage;
+use Composer\Package\Link;
+use Composer\Package\LinkConstraint\VersionConstraint;
 
 class ArrayDumperTest extends \PHPUnit_Framework_TestCase
 {
@@ -27,13 +29,13 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase
         $package = new MemoryPackage('foo', '1.0.0.0', '1.0');
 
         $config = $this->dumper->dump($package);
-        $this->assertEquals(array('name', 'version', 'version_normalized', 'type', 'names'), array_keys($config));
+        $this->assertEquals(array('name', 'version', 'version_normalized', 'type'), array_keys($config));
     }
 
     /**
      * @dataProvider getKeys
      */
-    public function testKeys($key, $value, $expectedValue = null, $method = null)
+    public function testKeys($key, $value, $method = null, $expectedValue = null)
     {
         $package = new MemoryPackage('foo', '1.0.0.0', '1.0');
 
@@ -50,17 +52,70 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase
     public function getKeys()
     {
         return array(
-            array('time', new \DateTime('2012-02-01'), '2012-02-01 00:00:00', 'ReleaseDate'),
-            array('authors', array('Nils Adermann <naderman@naderman.de>', 'Jordi Boggiano <j.boggiano@seld.be>')),
-            array('homepage', 'http://getcomposer.org'),
-            array('description', 'Package Manager'),
-            array('keywords', array('package', 'dependency', 'autoload')),
-            array('bin', array('bin/composer'), null, 'binaries'),
-            array('license', array('MIT')),
-            array('autoload', array('psr-0' => array('Composer' => 'src/'))),
-            array('repositories', array('packagist' => false)),
-            array('scripts', array('post-update-cmd' => 'MyVendor\\MyClass::postUpdate')),
-            array('extra', array('class' => 'MyVendor\\Installer')),
+            array(
+                'time',
+                new \DateTime('2012-02-01'),
+                'ReleaseDate',
+                '2012-02-01 00:00:00',
+            ),
+            array(
+                'authors',
+                array('Nils Adermann <naderman@naderman.de>', 'Jordi Boggiano <j.boggiano@seld.be>')
+            ),
+            array(
+                'homepage',
+                'http://getcomposer.org'
+            ),
+            array(
+                'description',
+                'Package Manager'
+            ),
+            array(
+                'keywords',
+                array('package', 'dependency', 'autoload')
+            ),
+            array(
+                'bin',
+                array('bin/composer'),
+                'binaries'
+            ),
+            array(
+                'license',
+                array('MIT')
+            ),
+            array(
+                'autoload',
+                array('psr-0' => array('Composer' => 'src/'))
+            ),
+            array(
+                'repositories',
+                array('packagist' => false)
+            ),
+            array(
+                'scripts',
+                array('post-update-cmd' => 'MyVendor\\MyClass::postUpdate')
+            ),
+            array(
+                'extra',
+                array('class' => 'MyVendor\\Installer')
+            ),
+            array(
+                'require',
+                array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires', '1.0.0')),
+                'requires',
+                array('foo/bar' => '1.0.0'),
+            ),
+            array(
+                'require-dev',
+                array(new Link('foo', 'foo/bar', new VersionConstraint('=', '1.0.0.0'), 'requires (for development)', '1.0.0')),
+                'devRequires',
+                array('foo/bar' => '1.0.0'),
+            ),
+            array(
+                'suggest',
+                array('foo/bar' => 'very useful package'),
+                'suggests'
+            ),
         );
     }
 }

+ 85 - 0
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -13,6 +13,7 @@
 namespace Composer\Test\Package\Loader;
 
 use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\Dumper\ArrayDumper;
 
 class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
 {
@@ -35,4 +36,88 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
         $replaces = $package->getReplaces();
         $this->assertEquals('== 1.2.3.4', (string) $replaces[0]->getConstraint());
     }
+
+    public function testTypeDefault()
+    {
+        $config = array(
+            'name' => 'A',
+            'version' => '1.0',
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertEquals('library', $package->getType());
+
+        $config = array(
+            'name' => 'A',
+            'version' => '1.0',
+            'type' => 'foo',
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertEquals('foo', $package->getType());
+    }
+
+    public function testNormalizedVersionOptimization()
+    {
+        $config = array(
+            'name' => 'A',
+            'version' => '1.2.3',
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertEquals('1.2.3.0', $package->getVersion());
+
+        $config = array(
+            'name' => 'A',
+            'version' => '1.2.3',
+            'version_normalized' => '1.2.3.4',
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertEquals('1.2.3.4', $package->getVersion());
+    }
+
+    public function testParseDump()
+    {
+        $config = array(
+            'name' => 'A/B',
+            'version' => '1.2.3',
+            'version_normalized' => '1.2.3.0',
+            'description' => 'Foo bar',
+            'type' => 'library',
+            'keywords' => array('a', 'b', 'c'),
+            'homepage' => 'http://example.com',
+            'license' => array('MIT', 'GPLv3'),
+            'authors' => array(
+                array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org'),
+            ),
+            'require' => array(
+                'foo/bar' => '1.0',
+            ),
+            'require-dev' => array(
+                'foo/baz' => '1.0',
+            ),
+            'replace' => array(
+                'foo/qux' => '1.0',
+            ),
+            'conflict' => array(
+                'foo/quux' => '1.0',
+            ),
+            'provide' => array(
+                'foo/quuux' => '1.0',
+            ),
+            'autoload' => array(
+                'psr-0' => array('Ns\Prefix' => 'path'),
+                'classmap' => array('path', 'path2'),
+            ),
+            'include-path' => array('path3', 'path4'),
+            'target-dir' => 'some/prefix',
+            'extra' => array('random' => array('things' => 'of', 'any' => 'shape')),
+            'bin' => array('bin1', 'bin/foo'),
+        );
+
+        $package = $this->loader->load($config);
+        $dumper = new ArrayDumper;
+        $this->assertEquals($config, $dumper->dump($package));
+    }
 }

+ 3 - 2
tests/Composer/Test/Package/LockerTest.php

@@ -151,10 +151,11 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                     array('package' => 'pkg1', 'version' => '1.0.0-beta'),
                     array('package' => 'pkg2', 'version' => '0.1.10')
                 ),
+                'packages-dev' => array(),
                 'aliases' => array(),
             ));
 
-        $locker->setLockData(array($package1, $package2), array());
+        $locker->setLockData(array($package1, $package2), array(), array());
     }
 
     public function testLockBadPackages()
@@ -172,7 +173,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
         $this->setExpectedException('LogicException');
 
-        $locker->setLockData(array($package1), array());
+        $locker->setLockData(array($package1), array(), array());
     }
 
     public function testIsFresh()

+ 1 - 1
tests/Composer/Test/Repository/FilesystemRepositoryTest.php

@@ -95,7 +95,7 @@ class FilesystemRepositoryTest extends TestCase
             ->expects($this->once())
             ->method('write')
             ->with(array(
-                array('name' => 'mypkg', 'type' => 'library', 'names' => array('mypkg'), 'version' => '0.1.10', 'version_normalized' => '0.1.10.0')
+                array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0')
             ));
 
         $repository->addPackage($this->getPackage('mypkg', '0.1.10'));