@ -34,6 +34,8 @@ use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\AliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\Link;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker;
use Composer\Package\Locker;
use Composer\Package\PackageInterface;
use Composer\Package\PackageInterface;
@ -192,13 +194,6 @@ class Installer
$this->downloadManager->setPreferSource($this->preferSource);
$this->downloadManager->setPreferSource($this->preferSource);
$this->downloadManager->setPreferDist($this->preferDist);
$this->downloadManager->setPreferDist($this->preferDist);
// clone root package to have one in the installed repo that does not require anything
// we don't want it to be uninstallable, but its requirements should not conflict
// with the lock file for example
$installedRootPackage = clone $this->package;
$installedRootPackage->setRequires(array());
$installedRootPackage->setDevRequires(array());
// create installed repo, this contains all local packages + platform packages (php & extensions)
// create installed repo, this contains all local packages + platform packages (php & extensions)
$localRepo = $this->repositoryManager->getLocalRepository();
$localRepo = $this->repositoryManager->getLocalRepository();
if ($this->update) {
if ($this->update) {
@ -207,15 +202,7 @@ class Installer
$platformOverrides = $this->locker->getPlatformOverrides();
$platformOverrides = $this->locker->getPlatformOverrides();
}
}
$platformRepo = new PlatformRepository(array(), $platformOverrides);
$platformRepo = new PlatformRepository(array(), $platformOverrides);
$repos = array(
$installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);
$localRepo,
new InstalledArrayRepository(array($installedRootPackage)),
$platformRepo,
);
$installedRepo = new CompositeRepository($repos);
if ($this->additionalInstalledRepository) {
$installedRepo->addRepository($this->additionalInstalledRepository);
}
$aliases = $this->getRootAliases();
$aliases = $this->getRootAliases();
$this->aliasPlatformPackages($platformRepo, $aliases);
$this->aliasPlatformPackages($platformRepo, $aliases);
@ -225,7 +212,7 @@ class Installer
}
}
try {
try {
$res = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $this->devMode );
list( $res, $devPackages) = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases);
if ($res !== 0) {
if ($res !== 0) {
return $res;
return $res;
}
}
@ -269,39 +256,11 @@ class Installer
if ($this->update) {
if ($this->update) {
$localRepo->reload();
$localRepo->reload();
// if this is not run in dev mode and the root has dev requires, the lock must
// contain null to prevent dev installs from a non-dev lock
$devPackages = ($this->devMode || !$this->package->getDevRequires()) ? array() : null;
// split dev and non-dev requirements by checking what would be removed if we update without the dev requirements
if ($this->devMode & & $this->package->getDevRequires()) {
$policy = $this->createPolicy();
$pool = $this->createPool(true);
$pool->addRepository($installedRepo, $aliases);
// creating requirements request
$request = $this->createRequest($this->package, $platformRepo);
$request->updateAll();
foreach ($this->package->getRequires() as $link) {
$request->install($link->getTarget(), $link->getConstraint());
}
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request);
$solver = new Solver($policy, $pool, $installedRepo, $this->io);
$ops = $solver->solve($request, $this->ignorePlatformReqs);
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);
foreach ($ops as $op) {
if ($op->getJobType() === 'uninstall') {
$devPackages[] = $op->getPackage();
}
}
}
$platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
$platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
$platformDevReqs = $this->devMode ? $this-> extractPlatformRequirements($this->package->getDevRequires()) : array( );
$platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());
$updatedLock = $this->locker->setLockData(
$updatedLock = $this->locker->setLockData(
array_diff($localRepo->getCanonicalPackages(), (array) $devPackages),
array_diff($localRepo->getCanonicalPackages(), $devPackages),
$devPackages,
$devPackages,
$platformReqs,
$platformReqs,
$platformDevReqs,
$platformDevReqs,
@ -358,10 +317,9 @@ class Installer
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $installedRepo
* @param PlatformRepository $platformRepo
* @param PlatformRepository $platformRepo
* @param array $aliases
* @param array $aliases
* @param bool $withDevReqs
* @return array [int, PackageInterfaces[]|null] with the exit code and an array of dev packages on update, or null on install
* @return int
*/
*/
protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases, $withDevReqs )
protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases)
{
{
// init vars
// init vars
$lockedRepository = null;
$lockedRepository = null;
@ -372,7 +330,7 @@ class Installer
// packages in that case
// packages in that case
if (!$this->update || (!empty($this->updateWhitelist) & & $this->locker->isLocked())) {
if (!$this->update || (!empty($this->updateWhitelist) & & $this->locker->isLocked())) {
try {
try {
$lockedRepository = $this->locker->getLockedRepository($withDevReqs );
$lockedRepository = $this->locker->getLockedRepository($this->devMode );
} catch (\RuntimeException $e) {
} catch (\RuntimeException $e) {
// if there are dev requires, then we really can not install
// if there are dev requires, then we really can not install
if ($this->package->getDevRequires()) {
if ($this->package->getDevRequires()) {
@ -385,7 +343,6 @@ class Installer
$this->whitelistUpdateDependencies(
$this->whitelistUpdateDependencies(
$localRepo,
$localRepo,
$withDevReqs,
$this->package->getRequires(),
$this->package->getRequires(),
$this->package->getDevRequires()
$this->package->getDevRequires()
);
);
@ -394,7 +351,7 @@ class Installer
// creating repository pool
// creating repository pool
$policy = $this->createPolicy();
$policy = $this->createPolicy();
$pool = $this->createPool($withDevReqs, $ this->update ? null : $lockedRepository);
$pool = $this->createPool($this->update ? null : $lockedRepository);
$pool->addRepository($installedRepo, $aliases);
$pool->addRepository($installedRepo, $aliases);
if ($this->update) {
if ($this->update) {
$repositories = $this->repositoryManager->getRepositories();
$repositories = $this->repositoryManager->getRepositories();
@ -425,15 +382,11 @@ class Installer
}
}
}
}
$this->io->writeError('< info > Updating dependencies'.($withDevReqs ? ' (including require-dev)' : '').'< / info > ');
$this->io->writeError('< info > Updating dependencies'.($this->devMode ? ' (including require-dev)' : '').'< / info > ');
$request->updateAll();
$request->updateAll();
if ($withDevReqs) {
$links = array_merge($this->package->getRequires(), $this->package->getDevRequires());
$links = array_merge($this->package->getRequires(), $this->package->getDevRequires());
} else {
$links = $this->package->getRequires();
}
foreach ($links as $link) {
foreach ($links as $link) {
$request->install($link->getTarget(), $link->getConstraint());
$request->install($link->getTarget(), $link->getConstraint());
@ -442,7 +395,7 @@ class Installer
// if the updateWhitelist is enabled, packages not in it are also fixed
// if the updateWhitelist is enabled, packages not in it are also fixed
// to the version specified in the lock, or their currently installed version
// to the version specified in the lock, or their currently installed version
if ($this->updateWhitelist) {
if ($this->updateWhitelist) {
$currentPackages = $this->getCurrentPackages($withDevReqs, $ installedRepo);
$currentPackages = $this->getCurrentPackages($installedRepo);
// collect packages to fixate from root requirements as well as installed packages
// collect packages to fixate from root requirements as well as installed packages
$candidates = array();
$candidates = array();
@ -471,7 +424,7 @@ class Installer
}
}
}
}
} else {
} else {
$this->io->writeError('< info > Installing dependencies'.($withDevReqs ? ' (including require-dev)' : '').' from lock file< / info > ');
$this->io->writeError('< info > Installing dependencies'.($this->devMode ? ' (including require-dev)' : '').' from lock file< / info > ');
if (!$this->locker->isFresh()) {
if (!$this->locker->isFresh()) {
$this->io->writeError('< warning > Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.< / warning > ', true, IOInterface::QUIET);
$this->io->writeError('< warning > Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.< / warning > ', true, IOInterface::QUIET);
@ -487,13 +440,13 @@ class Installer
$request->install($package->getName(), $constraint);
$request->install($package->getName(), $constraint);
}
}
foreach ($this->locker->getPlatformRequirements($withDevReqs ) as $link) {
foreach ($this->locker->getPlatformRequirements(true ) as $link) {
$request->install($link->getTarget(), $link->getConstraint());
$request->install($link->getTarget(), $link->getConstraint());
}
}
}
}
// force dev packages to have the latest links if we update or install from a (potentially new) lock
// force dev packages to have the latest links if we update or install from a (potentially new) lock
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $withDevReqs, 'force-links');
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links');
// solve dependencies
// solve dependencies
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);
@ -505,14 +458,14 @@ class Installer
$this->io->writeError('< error > Your requirements could not be resolved to an installable set of packages.< / error > ', true, IOInterface::QUIET);
$this->io->writeError('< error > Your requirements could not be resolved to an installable set of packages.< / error > ', true, IOInterface::QUIET);
$this->io->writeError($e->getMessage());
$this->io->writeError($e->getMessage());
return max(1, $e->getCode());
return array( max(1, $e->getCode()), array ());
}
}
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
$this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);
$this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);
// force dev packages to be updated if we update or install from a (potentially new) lock
// force dev packages to be updated if we update or install from a (potentially new) lock
$operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $withDevReqs, 'force-updates', $operations);
$operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations);
// execute operations
// execute operations
if (!$operations) {
if (!$operations) {
@ -522,6 +475,17 @@ class Installer
$operations = $this->movePluginsToFront($operations);
$operations = $this->movePluginsToFront($operations);
$operations = $this->moveUninstallsToFront($operations);
$operations = $this->moveUninstallsToFront($operations);
// extract dev packages and mark them to be skipped if it's a --no-dev install or update
// we also force them to be uninstalled if they are present in the local repo
if ($this->update) {
$devPackages = $this->extractDevPackages($operations, $localRepo, $platformRepo, $aliases);
if (!$this->devMode) {
$operations = $this->filterDevPackageOperations($devPackages, $operations, $localRepo);
}
} else {
$devPackages = null;
}
foreach ($operations as $operation) {
foreach ($operations as $operation) {
// collect suggestions
// collect suggestions
if ('install' === $operation->getJobType()) {
if ('install' === $operation->getJobType()) {
@ -604,7 +568,117 @@ class Installer
$localRepo->write();
$localRepo->write();
}
}
return 0;
return array(0, $devPackages);
}
/**
* Extracts the dev packages out of the localRepo
*
* This works by faking the operations so we can see what the dev packages
* would be at the end of the operation execution. This lets us then remove
* the dev packages from the list of operations accordingly if we are in a
* --no-dev install or update.
*
* @return array
*/
private function extractDevPackages(array $operations, RepositoryInterface $localRepo, PlatformRepository $platformRepo, array $aliases)
{
if (!$this->package->getDevRequires()) {
return array();
}
// fake-apply all operations to this clone of the local repo so we see the complete set of package we would end up with
$tempLocalRepo = clone $localRepo;
foreach ($operations as $operation) {
switch ($operation->getJobType()) {
case 'install':
case 'markAliasInstalled':
if (!$tempLocalRepo->hasPackage($operation->getPackage())) {
$tempLocalRepo->addPackage(clone $operation->getPackage());
}
break;
case 'uninstall':
case 'markAliasUninstalled':
$tempLocalRepo->removePackage($operation->getPackage());
break;
case 'update':
$tempLocalRepo->removePackage($operation->getInitialPackage());
if (!$tempLocalRepo->hasPackage($operation->getTargetPackage())) {
$tempLocalRepo->addPackage(clone $operation->getTargetPackage());
}
break;
default:
throw new \LogicException('Unknown type: '.$operation->getJobType());
}
}
// we have to reload the local repo to handle aliases properly
// but as it is not persisted on disk we use a loader/dumper
// to reload it in memory
$localRepo = new InstalledArrayRepository(array());
$loader = new ArrayLoader();
$dumper = new ArrayDumper();
foreach ($tempLocalRepo->getCanonicalPackages() as $pkg) {
$localRepo->addPackage($loader->load($dumper->dump($pkg)));
}
unset($tempLocalRepo, $loader, $dumper);
$policy = $this->createPolicy();
$pool = $this->createPool();
$installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);
$pool->addRepository($installedRepo, $aliases);
// creating requirements request without dev requirements
$request = $this->createRequest($this->package, $platformRepo);
$request->updateAll();
foreach ($this->package->getRequires() as $link) {
$request->install($link->getTarget(), $link->getConstraint());
}
// solve deps to see which get removed
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request);
$solver = new Solver($policy, $pool, $installedRepo, $this->io);
$ops = $solver->solve($request, $this->ignorePlatformReqs);
$this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);
$devPackages = array();
foreach ($ops as $op) {
if ($op->getJobType() === 'uninstall') {
$devPackages[] = $op->getPackage();
}
}
return $devPackages;
}
/**
* @return OperationInterface[] filtered operations, dev packages are uninstalled and all operations on them ignored
*/
private function filterDevPackageOperations(array $devPackages, array $operations, RepositoryInterface $localRepo)
{
$finalOps = array();
$packagesToSkip = array();
foreach ($devPackages as $pkg) {
$packagesToSkip[$pkg->getName()] = true;
if ($installedDevPkg = $localRepo->findPackage($pkg->getName(), '*')) {
$finalOps[] = new UninstallOperation($installedDevPkg, 'non-dev install removing it');
}
}
// skip operations applied on dev packages
foreach ($operations as $op) {
$package = $op->getJobType() === 'update' ? $op->getTargetPackage() : $op->getPackage();
if (isset($packagesToSkip[$package->getName()])) {
continue;
}
$finalOps[] = $op;
}
return $finalOps;
}
}
/**
/**
@ -672,20 +746,41 @@ class Installer
}
}
/**
/**
* @param bool $withDevReqs
* @return RepositoryInterface
*/
private function createInstalledRepo(RepositoryInterface $localRepo, PlatformRepository $platformRepo)
{
// clone root package to have one in the installed repo that does not require anything
// we don't want it to be uninstallable, but its requirements should not conflict
// with the lock file for example
$installedRootPackage = clone $this->package;
$installedRootPackage->setRequires(array());
$installedRootPackage->setDevRequires(array());
$repos = array(
$localRepo,
new InstalledArrayRepository(array($installedRootPackage)),
$platformRepo,
);
$installedRepo = new CompositeRepository($repos);
if ($this->additionalInstalledRepository) {
$installedRepo->addRepository($this->additionalInstalledRepository);
}
return $installedRepo;
}
/**
* @param RepositoryInterface|null $lockedRepository
* @param RepositoryInterface|null $lockedRepository
* @return Pool
* @return Pool
*/
*/
private function createPool($withDevReqs, RepositoryInterface $lockedRepository = null)
private function createPool(RepositoryInterface $lockedRepository = null)
{
{
if ($this->update) {
if ($this->update) {
$minimumStability = $this->package->getMinimumStability();
$minimumStability = $this->package->getMinimumStability();
$stabilityFlags = $this->package->getStabilityFlags();
$stabilityFlags = $this->package->getStabilityFlags();
$requires = $this->package->getRequires();
$requires = array_merge($this->package->getRequires(), $this->package->getDevRequires());
if ($withDevReqs) {
$requires = array_merge($requires, $this->package->getDevRequires());
}
} else {
} else {
$minimumStability = $this->locker->getMinimumStability();
$minimumStability = $this->locker->getMinimumStability();
$stabilityFlags = $this->locker->getStabilityFlags();
$stabilityFlags = $this->locker->getStabilityFlags();
@ -782,12 +877,11 @@ class Installer
* @param array $repositories
* @param array $repositories
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $lockedRepository
* @param RepositoryInterface $lockedRepository
* @param bool $withDevReqs
* @param string $task
* @param string $task
* @param array|null $operations
* @param array|null $operations
* @return array
* @return array
*/
*/
private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $withDevReqs, $ task, array $operations = null)
private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null)
{
{
if ($task === 'force-updates' & & null === $operations) {
if ($task === 'force-updates' & & null === $operations) {
throw new \InvalidArgumentException('Missing operations argument');
throw new \InvalidArgumentException('Missing operations argument');
@ -797,7 +891,7 @@ class Installer
}
}
if ($this->update & & $this->updateWhitelist) {
if ($this->update & & $this->updateWhitelist) {
$currentPackages = $this->getCurrentPackages($withDevReqs, $ installedRepo);
$currentPackages = $this->getCurrentPackages($installedRepo);
}
}
foreach ($localRepo->getCanonicalPackages() as $package) {
foreach ($localRepo->getCanonicalPackages() as $package) {
@ -916,15 +1010,14 @@ class Installer
/**
/**
* Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback
* Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback
* @param bool $withDevReqs
* @param RepositoryInterface $installedRepo
* @param RepositoryInterface $installedRepo
* @return array
* @return array
*/
*/
private function getCurrentPackages($withDevReqs, $ installedRepo)
private function getCurrentPackages($installedRepo)
{
{
if ($this->locker->isLocked()) {
if ($this->locker->isLocked()) {
try {
try {
return $this->locker->getLockedRepository($withDevReqs )->getPackages();
return $this->locker->getLockedRepository(true )->getPackages();
} catch (\RuntimeException $e) {
} catch (\RuntimeException $e) {
// fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
// fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
return $this->locker->getLockedRepository()->getPackages();
return $this->locker->getLockedRepository()->getPackages();
@ -1109,25 +1202,22 @@ class Installer
* update whitelist themselves.
* update whitelist themselves.
*
*
* @param RepositoryInterface $localRepo
* @param RepositoryInterface $localRepo
* @param bool $devMode
* @param array $rootRequires An array of links to packages in require of the root package
* @param array $rootRequires An array of links to packages in require of the root package
* @param array $rootDevRequires An array of links to packages in require-dev of the root package
* @param array $rootDevRequires An array of links to packages in require-dev of the root package
*/
*/
private function whitelistUpdateDependencies($localRepo, $devMode, array $rootRequires, array $rootDevRequires)
private function whitelistUpdateDependencies($localRepo, array $rootRequires, array $rootDevRequires)
{
{
if (!$this->updateWhitelist) {
if (!$this->updateWhitelist) {
return;
return;
}
}
$rootRequires = array_merge($rootRequires, $rootDevRequires);
$requiredPackageNames = array();
$requiredPackageNames = array();
foreach (array_merge($rootRequires, $rootDevRequires) as $require) {
foreach ($rootRequires as $require) {
$requiredPackageNames[] = $require->getTarget();
$requiredPackageNames[] = $require->getTarget();
}
}
if ($devMode) {
$rootRequires = array_merge($rootRequires, $rootDevRequires);
}
$skipPackages = array();
$skipPackages = array();
foreach ($rootRequires as $require) {
foreach ($rootRequires as $require) {
$skipPackages[$require->getTarget()] = true;
$skipPackages[$require->getTarget()] = true;