diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b828836bc..d26d13126 100644 --- a/src/Composer/Installer.php +++ b/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('Loading composer repositories with package information'); // 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() diff --git a/tests/Composer/Test/Fixtures/installer/update-dev-to-new-ref-picks-up-changes.test b/tests/Composer/Test/Fixtures/installer/update-dev-to-new-ref-picks-up-changes.test new file mode 100644 index 000000000..fa2146345 --- /dev/null +++ b/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) diff --git a/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test b/tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test new file mode 100644 index 000000000..f5c4ccc24 --- /dev/null +++ b/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)