Browse Source

Fix updates when dev packages have new dependencies (no more double updates needed), fixes #1105

Jordi Boggiano 12 years ago
parent
commit
f1f1ddb66b

+ 110 - 73
src/Composer/Installer.php

@@ -239,6 +239,10 @@ class Installer
         $minimumStability = $this->package->getMinimumStability();
         $stabilityFlags = $this->package->getStabilityFlags();
 
+        // init vars
+        $lockedRepository = null;
+        $repositories = null;
+
         // initialize locker to create aliased packages
         $installFromLock = false;
         if (!$this->update && $this->locker->isLocked($devMode)) {
@@ -258,6 +262,7 @@ class Installer
         $this->io->write('<info>Loading composer repositories with package information</info>');
 
         // creating repository pool
+        $policy = new DefaultPolicy();
         $pool = new Pool($minimumStability, $stabilityFlags);
         $pool->addRepository($installedRepo, $aliases);
         if ($installFromLock) {
@@ -364,11 +369,11 @@ class Installer
             }
         }
 
-        // prepare solver
-        $policy = new DefaultPolicy();
-        $solver = new Solver($policy, $pool, $installedRepo);
+        // force dev packages to have the latest links if we update or install from a (potentially new) lock
+        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
 
         // solve dependencies
+        $solver = new Solver($policy, $pool, $installedRepo);
         try {
             $operations = $solver->solve($request);
         } catch (SolverProblemsException $e) {
@@ -392,6 +397,76 @@ class Installer
         }
 
         // force dev packages to be updated if we update or install from a (potentially new) lock
+        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-updates', $operations);
+
+        // execute operations
+        if (!$operations) {
+            $this->io->write('Nothing to install or update');
+        }
+
+        foreach ($operations as $operation) {
+            // collect suggestions
+            if ('install' === $operation->getJobType()) {
+                foreach ($operation->getPackage()->getSuggests() as $target => $reason) {
+                    $this->suggestedPackages[] = array(
+                        'source' => $operation->getPackage()->getPrettyName(),
+                        'target' => $target,
+                        'reason' => $reason,
+                    );
+                }
+            }
+
+            $event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());
+            if (defined($event) && $this->runScripts) {
+                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
+            }
+
+            // not installing from lock, force dev packages' references if they're in root package refs
+            if (!$installFromLock) {
+                $package = null;
+                if ('update' === $operation->getJobType()) {
+                    $package = $operation->getTargetPackage();
+                } elseif ('install' === $operation->getJobType()) {
+                    $package = $operation->getPackage();
+                }
+                if ($package && $package->isDev()) {
+                    $references = $this->package->getReferences();
+                    if (isset($references[$package->getName()])) {
+                        $package->setSourceReference($references[$package->getName()]);
+                        $package->setDistReference($references[$package->getName()]);
+                    }
+                }
+            }
+
+            // output alias operations in verbose mode, or all ops in dry run
+            if ($this->dryRun || ($this->verbose && false !== strpos($operation->getJobType(), 'Alias'))) {
+                $this->io->write('  - ' . $operation);
+            }
+
+            $this->installationManager->execute($localRepo, $operation);
+
+            $event = 'Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType());
+            if (defined($event) && $this->runScripts) {
+                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
+            }
+
+            if (!$this->dryRun) {
+                $localRepo->write();
+            }
+        }
+
+        return true;
+    }
+
+    private function processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, $task, array $operations = null)
+    {
+        if ($task === 'force-updates' && null === $operations) {
+            throw new \InvalidArgumentException('Missing operations argument');
+        }
+        if ($task === 'force-links') {
+            $operations = array();
+        }
+
         foreach ($localRepo->getPackages() as $package) {
             // skip non-dev packages
             if (!$package->isDev()) {
@@ -414,14 +489,19 @@ class Installer
             // force update to locked version if it does not match the installed version
             if ($installFromLock) {
                 foreach ($lockedRepository->findPackages($package->getName()) as $lockedPackage) {
-                    if (
-                        $lockedPackage->isDev()
-                        && (
-                            ($lockedPackage->getSourceReference() && $lockedPackage->getSourceReference() !== $package->getSourceReference())
-                            || ($lockedPackage->getDistReference() && $lockedPackage->getDistReference() !== $package->getDistReference())
-                        )
-                    ) {
-                        $operations[] = new UpdateOperation($package, $lockedPackage);
+                    if ($lockedPackage->isDev() && $lockedPackage->getVersion() === $package->getVersion()) {
+                        if ($task === 'force-links') {
+                            $package->setRequires($lockedPackage->getRequires());
+                            $package->setConflicts($lockedPackage->getConflicts());
+                            $package->setProvides($lockedPackage->getProvides());
+                            $package->setReplaces($lockedPackage->getReplaces());
+                        } elseif ($task === 'force-updates') {
+                            if (($lockedPackage->getSourceReference() && $lockedPackage->getSourceReference() !== $package->getSourceReference())
+                                || ($lockedPackage->getDistReference() && $lockedPackage->getDistReference() !== $package->getDistReference())
+                            ) {
+                                $operations[] = new UpdateOperation($package, $lockedPackage);
+                            }
+                        }
 
                         break;
                     }
@@ -456,79 +536,36 @@ class Installer
                     if ($matches && $matches = $policy->selectPreferedPackages($pool, array(), $matches)) {
                         $newPackage = $pool->literalToPackage($matches[0]);
 
-                        if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
+                        if ($task === 'force-links' && $newPackage) {
+                            $package->setRequires($newPackage->getRequires());
+                            $package->setConflicts($newPackage->getConflicts());
+                            $package->setProvides($newPackage->getProvides());
+                            $package->setReplaces($newPackage->getReplaces());
+                        }
+
+                        if ($task === 'force-updates' && $newPackage && (
+                            (($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference())
+                                || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference())
+                            )
+                        )) {
                             $operations[] = new UpdateOperation($package, $newPackage);
                         }
                     }
                 }
 
-                // force installed package to update to referenced version if it does not match the installed version
-                $references = $this->package->getReferences();
-
-                if (isset($references[$package->getName()]) && $references[$package->getName()] !== $package->getSourceReference()) {
-                    // changing the source ref to update to will be handled in the operations loop below
-                    $operations[] = new UpdateOperation($package, clone $package);
-                }
-            }
-        }
-
-        // execute operations
-        if (!$operations) {
-            $this->io->write('Nothing to install or update');
-        }
-
-        foreach ($operations as $operation) {
-            // collect suggestions
-            if ('install' === $operation->getJobType()) {
-                foreach ($operation->getPackage()->getSuggests() as $target => $reason) {
-                    $this->suggestedPackages[] = array(
-                        'source' => $operation->getPackage()->getPrettyName(),
-                        'target' => $target,
-                        'reason' => $reason,
-                    );
-                }
-            }
-
-            $event = 'Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());
-            if (defined($event) && $this->runScripts) {
-                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
-            }
-
-            // not installing from lock, force dev packages' references if they're in root package refs
-            if (!$installFromLock) {
-                $package = null;
-                if ('update' === $operation->getJobType()) {
-                    $package = $operation->getTargetPackage();
-                } elseif ('install' === $operation->getJobType()) {
-                    $package = $operation->getPackage();
-                }
-                if ($package && $package->isDev()) {
+                if ($task === 'force-updates') {
+                    // force installed package to update to referenced version if it does not match the installed version
                     $references = $this->package->getReferences();
-                    if (isset($references[$package->getName()])) {
-                        $package->setSourceReference($references[$package->getName()]);
-                        $package->setDistReference($references[$package->getName()]);
+
+                    if (isset($references[$package->getName()]) && $references[$package->getName()] !== $package->getSourceReference()) {
+                        // changing the source ref to update to will be handled in the operations loop below
+                        $operations[] = new UpdateOperation($package, clone $package);
                     }
                 }
             }
-
-            // output alias operations in verbose mode, or all ops in dry run
-            if ($this->dryRun || ($this->verbose && false !== strpos($operation->getJobType(), 'Alias'))) {
-                $this->io->write('  - ' . $operation);
-            }
-
-            $this->installationManager->execute($localRepo, $operation);
-
-            $event = 'Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType());
-            if (defined($event) && $this->runScripts) {
-                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $operation);
-            }
-
-            if (!$this->dryRun) {
-                $localRepo->write();
-            }
         }
 
-        return true;
+        return $operations;
     }
 
     private function getRootAliases()

+ 41 - 0
tests/Composer/Test/Fixtures/installer/update-dev-to-new-ref-picks-up-changes.test

@@ -0,0 +1,41 @@
+--TEST--
+Updating a dev package to its latest ref should pick up new dependencies
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                {
+                    "name": "a/devpackage", "version": "dev-master",
+                    "source": { "reference": "newref", "url": "", "type": "git" },
+                    "require": {
+                        "a/dependency": "*"
+                    }
+                },
+                {
+                    "name": "a/dependency", "version": "dev-master",
+                    "source": { "reference": "ref", "url": "", "type": "git" },
+                    "require": {}
+                }
+            ]
+        }
+    ],
+    "require": {
+        "a/devpackage": "dev-master"
+    },
+    "minimum-stability": "dev"
+}
+--INSTALLED--
+[
+    {
+        "name": "a/devpackage", "version": "dev-master",
+        "source": { "reference": "oldref", "url": "", "type": "git" },
+        "require": {}
+    }
+]
+--RUN--
+update
+--EXPECT--
+Installing a/dependency (dev-master ref)
+Updating a/devpackage (dev-master oldref) to a/devpackage (dev-master newref)

+ 43 - 0
tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test

@@ -0,0 +1,43 @@
+--TEST--
+Installing locked dev packages should remove old dependencies
+--COMPOSER--
+{
+    "require": {
+        "a/devpackage": "dev-master"
+    },
+    "minimum-stability": "dev"
+}
+--LOCK--
+{
+    "packages": [
+        {
+            "name": "a/devpackage", "version": "dev-master",
+            "source": { "reference": "newref", "url": "", "type": "git" },
+            "require": {}
+        }
+    ],
+    "packages-dev": null,
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": []
+}
+--INSTALLED--
+[
+    {
+        "name": "a/devpackage", "version": "dev-master",
+        "source": { "reference": "oldref", "url": "", "type": "git" },
+        "require": {
+            "a/dependency": "*"
+        }
+    },
+    {
+        "name": "a/dependency", "version": "dev-master",
+        "source": { "reference": "ref", "url": "", "type": "git" },
+        "require": {}
+    }
+]
+--RUN--
+install
+--EXPECT--
+Uninstalling a/dependency (dev-master ref)
+Updating a/devpackage (dev-master oldref) to a/devpackage (dev-master newref)