diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 1ccd3a80c..3c4547be7 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -27,6 +27,7 @@ use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Semver\Constraint\MultiConstraint; +use Composer\Semver\Intervals; /** * @author Nils Adermann @@ -57,16 +58,22 @@ class PoolBuilder * @var IOInterface */ private $io; - /** * @psalm-var array */ private $aliasMap = array(); /** - * @psalm-var array + * @psalm-var array + */ + private $packagesToLoad = array(); + /** + * @psalm-var array + */ + private $loadedPackages = array(); + /** + * @psalm-var array>> */ - private $nameConstraints = array(); - private $loadedNames = array(); + private $loadedPerRepo = array(); /** * @psalm-var Package[] */ @@ -77,11 +84,23 @@ class PoolBuilder private $unacceptableFixedPackages = array(); private $updateAllowList = array(); private $skippedLoad = array(); + + /** + * Keeps a list of dependencies which are root requirements, and as such + * have already their maximum required range loaded and can not be + * extended by markPackageNameForLoading + * + * Packages get cleared from this list if they get unfixed as in that case + * we need to actually load them + */ + private $maxExtendedReqs = array(); /** * @psalm-var array */ private $updateAllowWarned = array(); + private $indexCounter = 0; + /** * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value * @psalm-param array $acceptableStabilities @@ -121,15 +140,14 @@ class PoolBuilder } } - $loadNames = array(); foreach ($request->getFixedPackages() as $package) { - $this->nameConstraints[$package->getName()] = null; - $this->loadedNames[$package->getName()] = true; + // using MatchAllConstraint here because fixed packages do not need to retrigger + // loading any packages + $this->loadedPackages[$package->getName()] = new MatchAllConstraint(); // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways foreach ($package->getReplaces() as $link) { - $this->nameConstraints[$package->getName()] = null; - $this->loadedNames[$link->getTarget()] = true; + $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint(); } // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to @@ -140,7 +158,7 @@ class PoolBuilder || $package->getRepository() instanceof PlatformRepository || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability()) ) { - $loadNames += $this->loadPackage($request, $package, false); + $this->loadPackage($request, $package, false); } else { $this->unacceptableFixedPackages[] = $package; } @@ -148,60 +166,30 @@ class PoolBuilder foreach ($request->getRequires() as $packageName => $constraint) { // fixed packages have already been added, so if a root require needs one of them, no need to do anything - if (isset($this->loadedNames[$packageName])) { + if (isset($this->loadedPackages[$packageName])) { continue; } - $loadNames[$packageName] = $constraint; - $this->nameConstraints[$packageName] = $constraint && !($constraint instanceof MatchAllConstraint) ? array($constraint) : null; + $this->packagesToLoad[$packageName] = $constraint; + $this->maxExtendedReqs[$packageName] = true; } - // clean up loadNames for anything we manually marked loaded above - foreach ($loadNames as $name => $void) { - if (isset($this->loadedNames[$name])) { - unset($loadNames[$name]); + // clean up packagesToLoad for anything we manually marked loaded above + foreach ($this->packagesToLoad as $name => $constraint) { + if (isset($this->loadedPackages[$name])) { + unset($this->packagesToLoad[$name]); } } - while (!empty($loadNames)) { - foreach ($loadNames as $name => $void) { - $this->loadedNames[$name] = true; - } - - $newLoadNames = array(); - foreach ($repositories as $repository) { - // these repos have their packages fixed if they need to be loaded so we - // never need to load anything else from them - if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { - continue; - } - $result = $repository->loadPackages($loadNames, $this->acceptableStabilities, $this->stabilityFlags); - - foreach ($result['namesFound'] as $name) { - // avoid loading the same package again from other repositories once it has been found - unset($loadNames[$name]); - } - foreach ($result['packages'] as $package) { - $newLoadNames += $this->loadPackage($request, $package); - } - } - - $loadNames = $newLoadNames; + while (!empty($this->packagesToLoad)) { + $this->loadPackagesMarkedForLoading($request, $repositories); } - // filter packages according to all the require statements collected for each package - $nameConstraints = array(); - foreach ($this->nameConstraints as $name => $constraints) { - if (\is_array($constraints)) { - $nameConstraints[$name] = MultiConstraint::create(array_values(array_unique($constraints)), false); - } - } foreach ($this->packages as $i => $package) { // we check all alias related packages at once, so no need to check individual aliases // isset also checks non-null value - if (!$package instanceof AliasPackage && isset($nameConstraints[$package->getName()])) { - $constraint = $nameConstraints[$package->getName()]; - + if (!$package instanceof AliasPackage) { + $constraint = new Constraint('==', $package->getVersion()); $aliasedPackages = array($i => $package); if (isset($this->aliasMap[spl_object_hash($package)])) { $aliasedPackages += $this->aliasMap[spl_object_hash($package)]; @@ -241,19 +229,111 @@ class PoolBuilder $pool = new Pool($this->packages, $this->unacceptableFixedPackages); $this->aliasMap = array(); - $this->nameConstraints = array(); - $this->loadedNames = array(); + $this->packagesToLoad = array(); + $this->loadedPackages = array(); + $this->loadedPerRepo = array(); $this->packages = array(); $this->unacceptableFixedPackages = array(); + $this->maxExtendedReqs = array(); + $this->skippedLoad = array(); + $this->indexCounter = 0; + + Intervals::clear(); return $pool; } + private function markPackageNameForLoading(Request $request, $name, ConstraintInterface $constraint) + { + // Skip platform requires at this stage + if (PlatformRepository::isPlatformPackage($name)) { + return; + } + + // Root require (which was not unfixed) already loaded the maximum range so no + // need to check anything here + if (isset($this->maxExtendedReqs[$name])) { + return; + } + + // Root requires can not be overruled by dependencies so there is no point in + // extending the loaded constraint for those. + // This is triggered when loading a root require which was fixed but got unfixed, then + // we make sure that we load at most the intervals covered by the root constraint. + $rootRequires = $request->getRequires(); + if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) { + $constraint = $rootRequires[$name]; + } + + // Not yet loaded or already marked for a reload, override the existing constraint + // (either it's a new one to load, or it has already been extended above) + if (!isset($this->loadedPackages[$name])) { + // Maybe it was already marked before but not loaded yet. In that case + // we have to extend the constraint (we don't check if they are identical because + // MultiConstraint::create() will optimize anyway) + if (isset($this->packagesToLoad[$name])) { + // Already marked for loading and this does not expand the constraint to be loaded, nothing to do + if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) { + return; + } + + // extend the constraint to be loaded + $constraint = Intervals::compactConstraint(MultiConstraint::create(array($this->packagesToLoad[$name], $constraint), false)); + } + + $this->packagesToLoad[$name] = $constraint; + return; + } + + // No need to load this package with this constraint because it is + // a subset of the constraint with which we have already loaded packages + if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) { + return; + } + + // We have already loaded that package but not in the constraint that's + // required. We extend the constraint and mark that package as not being loaded + // yet so we get the required package versions + $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create(array($this->loadedPackages[$name], $constraint), false)); + unset($this->loadedPackages[$name]); + } + + private function loadPackagesMarkedForLoading(Request $request, $repositories) + { + foreach ($this->packagesToLoad as $name => $constraint) { + $this->loadedPackages[$name] = $constraint; + } + + $packageBatch = $this->packagesToLoad; + $this->packagesToLoad = array(); + + foreach ($repositories as $repoIndex => $repository) { + if (empty($packageBatch)) { + break; + } + + // these repos have their packages fixed if they need to be loaded so we + // never need to load anything else from them + if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) { + continue; + } + $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, isset($this->loadedPerRepo[$repoIndex]) ? $this->loadedPerRepo[$repoIndex] : array()); + + foreach ($result['namesFound'] as $name) { + // avoid loading the same package again from other repositories once it has been found + unset($packageBatch[$name]); + } + foreach ($result['packages'] as $package) { + $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package; + $this->loadPackage($request, $package); + } + } + } + private function loadPackage(Request $request, PackageInterface $package, $propagateUpdate = true) { - end($this->packages); - $index = key($this->packages) + 1; - $this->packages[] = $package; + $index = $this->indexCounter++; + $this->packages[$index] = $package; if ($package instanceof AliasPackage) { $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package; @@ -283,37 +363,35 @@ class PoolBuilder $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']); $aliasPackage->setRootPackageAlias(true); - $this->packages[] = $aliasPackage; - $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$index+1] = $aliasPackage; + $newIndex = $this->indexCounter++; + $this->packages[$newIndex] = $aliasPackage; + $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage; } - $loadNames = array(); foreach ($package->getRequires() as $link) { $require = $link->getTarget(); - if (!isset($this->loadedNames[$require])) { - $loadNames[$require] = null; - // if this is a partial update with transitive dependencies we need to unfix the package we now know is a - // dependency of another package which we are trying to update, and then attempt to load it again - } elseif ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies() && isset($this->skippedLoad[$require])) { - if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$require])) { - $this->unfixPackage($request, $require); - $loadNames[$require] = null; - } elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $require) && !isset($this->updateAllowWarned[$require])) { - $this->updateAllowWarned[$require] = true; - $this->io->writeError('Dependency "'.$require.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.'); - } - } - $linkConstraint = $link->getConstraint(); - if ($linkConstraint && !($linkConstraint instanceof MatchAllConstraint)) { - if (!\array_key_exists($require, $this->nameConstraints)) { - $this->nameConstraints[$require] = array($linkConstraint); - } elseif (\is_array($this->nameConstraints[$require])) { - $this->nameConstraints[$require][] = $linkConstraint; + + if ($propagateUpdate) { + // if this is a partial update with transitive dependencies we need to unfix the package we now know is a + // dependency of another package which we are trying to update, and then attempt to load it again + if ($request->getUpdateAllowTransitiveDependencies() && isset($this->skippedLoad[$require])) { + if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$require])) { + $this->unfixPackage($request, $require); + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $require) && !isset($this->updateAllowWarned[$require])) { + $this->updateAllowWarned[$require] = true; + $this->io->writeError('Dependency "'.$require.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.'); + } + } else { + $this->markPackageNameForLoading($request, $require, $linkConstraint); } - // else it is null and should stay null } else { - $this->nameConstraints[$require] = null; + // We also need to load the requirements of a fixed package + // unless it was skipped + if (!isset($this->skippedLoad[$require])) { + $this->markPackageNameForLoading($request, $require, $linkConstraint); + } } } @@ -322,12 +400,10 @@ class PoolBuilder if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) { foreach ($package->getReplaces() as $link) { $replace = $link->getTarget(); - if (isset($this->loadedNames[$replace]) && isset($this->skippedLoad[$replace])) { + if (isset($this->loadedPackages[$replace]) && isset($this->skippedLoad[$replace])) { if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$replace])) { $this->unfixPackage($request, $replace); - $loadNames[$replace] = null; - // TODO should we try to merge constraints here? - $this->nameConstraints[$replace] = null; + $this->markPackageNameForLoading($request, $replace, $link->getConstraint()); } elseif (!$request->getUpdateAllowTransitiveRootDependencies() && $this->isRootRequire($request, $replace) && !isset($this->updateAllowWarned[$replace])) { $this->updateAllowWarned[$replace] = true; $this->io->writeError('Dependency "'.$replace.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies to include root dependencies.'); @@ -335,8 +411,6 @@ class PoolBuilder } } } - - return $loadNames; } /** @@ -401,14 +475,7 @@ class PoolBuilder if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) { if (false !== $index = array_search($lockedPackage, $this->packages, true)) { $request->unfixPackage($lockedPackage); - unset($this->packages[$index]); - if (isset($this->aliasMap[spl_object_hash($lockedPackage)])) { - foreach ($this->aliasMap[spl_object_hash($lockedPackage)] as $aliasIndex => $aliasPackage) { - $request->unfixPackage($aliasPackage); - unset($this->packages[$aliasIndex]); - } - unset($this->aliasMap[spl_object_hash($lockedPackage)]); - } + $this->removeLoadedPackage($request, $lockedPackage, $index); } } } @@ -423,7 +490,20 @@ class PoolBuilder } unset($this->skippedLoad[$name]); - unset($this->loadedNames[$name]); + unset($this->loadedPackages[$name]); + unset($this->maxExtendedReqs[$name]); + } + + private function removeLoadedPackage(Request $request, PackageInterface $package, $index) + { + unset($this->packages[$index]); + if (isset($this->aliasMap[spl_object_hash($package)])) { + foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) { + $request->unfixPackage($aliasPackage); + unset($this->packages[$aliasIndex]); + } + unset($this->aliasMap[spl_object_hash($package)]); + } } } diff --git a/src/Composer/DependencyResolver/Request.php b/src/Composer/DependencyResolver/Request.php index 5782c3ff1..c3367e3d9 100644 --- a/src/Composer/DependencyResolver/Request.php +++ b/src/Composer/DependencyResolver/Request.php @@ -17,6 +17,7 @@ use Composer\Package\PackageInterface; use Composer\Package\RootAliasPackage; use Composer\Repository\LockArrayRepository; use Composer\Semver\Constraint\ConstraintInterface; +use Composer\Semver\Constraint\MatchAllConstraint; /** * @author Nils Adermann @@ -55,6 +56,13 @@ class Request public function requireName($packageName, ConstraintInterface $constraint = null) { $packageName = strtolower($packageName); + + if ($constraint === null) { + $constraint = new MatchAllConstraint(); + } + if (isset($this->requires[$packageName])) { + throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyConstraint().' => '.$constraint->getPrettyConstraint().', check why it is happening, might be a root alias'); + } $this->requires[$packageName] = $constraint; } diff --git a/src/Composer/Package/Link.php b/src/Composer/Package/Link.php index 5a6c683cc..4af6a5a6a 100644 --- a/src/Composer/Package/Link.php +++ b/src/Composer/Package/Link.php @@ -32,7 +32,7 @@ class Link protected $target; /** - * @var ConstraintInterface|null + * @var ConstraintInterface */ protected $constraint; @@ -49,13 +49,13 @@ class Link /** * Creates a new package link. * - * @param string $source - * @param string $target - * @param ConstraintInterface|null $constraint Constraint applying to the target of this link - * @param string $description Used to create a descriptive string representation - * @param string|null $prettyConstraint + * @param string $source + * @param string $target + * @param ConstraintInterface $constraint Constraint applying to the target of this link + * @param string $description Used to create a descriptive string representation + * @param string|null $prettyConstraint */ - public function __construct($source, $target, ConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null) + public function __construct($source, $target, ConstraintInterface $constraint, $description = 'relates to', $prettyConstraint = null) { $this->source = strtolower($source); $this->target = strtolower($target); @@ -89,7 +89,7 @@ class Link } /** - * @return ConstraintInterface|null + * @return ConstraintInterface */ public function getConstraint() { diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 37a241cbc..24a7a38d8 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -50,7 +50,7 @@ class ArrayRepository implements RepositoryInterface /** * {@inheritDoc} */ - public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags) + public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array()) { $packages = $this->getPackages(); @@ -61,6 +61,7 @@ class ArrayRepository implements RepositoryInterface if ( (!$packageMap[$package->getName()] || $packageMap[$package->getName()]->matches(new Constraint('==', $package->getVersion()))) && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability()) + && !isset($alreadyLoaded[$package->getName()][$package->getVersion()]) ) { // add selected packages which match stability requirements $result[spl_object_hash($package)] = $package; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 9b244f397..f9018136b 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -341,13 +341,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito return $names; } - public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags) + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array()) { // this call initializes loadRootServerFile which is needed for the rest below to work $hasProviders = $this->hasProviders(); if (!$hasProviders && !$this->hasPartialPackages() && !$this->lazyProvidersUrl) { - return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags); + return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); } $packages = array(); @@ -363,12 +363,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito continue; } - $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags); + $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); foreach ($candidates as $candidate) { if ($candidate->getName() !== $name) { throw new \LogicException('whatProvides should never return a package with a different name than the requested one'); } $namesFound[$name] = true; + if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) { $matches[spl_object_hash($candidate)] = $candidate; if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) { @@ -401,7 +402,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags); + $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages = array_merge($packages, $result['packages']); $namesFound = array_merge($namesFound, $result['namesFound']); } @@ -531,7 +532,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito * @param string $name package name * @return array|mixed */ - private function whatProvides($name, array $acceptableStabilities = null, array $stabilityFlags = null) + private function whatProvides($name, array $acceptableStabilities = null, array $stabilityFlags = null, array $alreadyLoaded = array()) { if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { // skip platform packages, root package and composer-plugin-api @@ -623,6 +624,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $version['version_normalized'] = $this->versionParser->normalize($version['version']); } + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$name][$version['version_normalized']])) { + continue; + } + if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[$version['uid']] = $version; } @@ -680,7 +686,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito /** * @param array $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded */ - private function loadAsyncPackages(array $packageNames, array $acceptableStabilities = null, array $stabilityFlags = null) + private function loadAsyncPackages(array $packageNames, array $acceptableStabilities = null, array $stabilityFlags = null, array $alreadyLoaded = array()) { $this->loadRootServerFile(); @@ -723,7 +729,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) - ->then(function ($response) use (&$packages, &$namesFound, $contents, $realName, $constraint, $repo, $acceptableStabilities, $stabilityFlags) { + ->then(function ($response) use (&$packages, &$namesFound, $contents, $realName, $constraint, $repo, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) { if (true === $response) { $response = $contents; } @@ -748,6 +754,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $version['version_normalized'] = $repo->versionParser->normalize($version['version']); } + // avoid loading packages which have already been loaded + if (isset($alreadyLoaded[$realName][$version['version_normalized']])) { + continue; + } + if ($repo->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) { $versionsToLoad[] = $version; } diff --git a/src/Composer/Repository/CompositeRepository.php b/src/Composer/Repository/CompositeRepository.php index acabaffb0..eaa70db2b 100644 --- a/src/Composer/Repository/CompositeRepository.php +++ b/src/Composer/Repository/CompositeRepository.php @@ -102,13 +102,13 @@ class CompositeRepository implements RepositoryInterface /** * {@inheritDoc} */ - public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags) + public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array()) { $packages = array(); $namesFound = array(); foreach ($this->repositories as $repository) { /* @var $repository RepositoryInterface */ - $result = $repository->loadPackages($packageMap, $acceptableStabilities, $stabilityFlags); + $result = $repository->loadPackages($packageMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); $packages[] = $result['packages']; $namesFound[] = $result['namesFound']; } diff --git a/src/Composer/Repository/FilterRepository.php b/src/Composer/Repository/FilterRepository.php index 5221e55cb..361c2fe67 100644 --- a/src/Composer/Repository/FilterRepository.php +++ b/src/Composer/Repository/FilterRepository.php @@ -108,7 +108,7 @@ class FilterRepository implements RepositoryInterface /** * {@inheritDoc} */ - public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags) + public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array()) { foreach ($packageMap as $name => $constraint) { if (!$this->isAllowed($name)) { @@ -120,7 +120,7 @@ class FilterRepository implements RepositoryInterface return array('namesFound' => array(), 'packages' => array()); } - $result = $this->repo->loadPackages($packageMap, $acceptableStabilities, $stabilityFlags); + $result = $this->repo->loadPackages($packageMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded); if (!$this->canonical) { $result['namesFound'] = array(); } diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index ac6d49525..f6ec91864 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -593,4 +593,21 @@ class PlatformRepository extends ArrayRepository $this->addPackage($lib); } + + /** + * Check if a package name is a platform package. + * + * @param $name + * @return bool + */ + public static function isPlatformPackage($name) + { + static $cache = array(); + + if (isset($cache[$name])) { + return $cache[$name]; + } + + return $cache[$name] = (bool) preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name); + } } diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index afb99ca62..54e6e04a6 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -75,11 +75,13 @@ interface RepositoryInterface extends \Countable * @psalm-param array $acceptableStabilities * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value * @psalm-param array $stabilityFlags + * @param array[] $alreadyLoaded an array of package name => package version => package + * @psalm-param array> $alreadyLoaded * * @return array [namesFound => string[], packages => PackageInterface[]] * @psalm-return array{namesFound: string[], packages: PackageInterface[]} */ - public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags); + public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array()); /** * Searches the repository for packages containing the query diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index c178e93d8..35dcd37da 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -16,6 +16,7 @@ use Composer\Autoload\AutoloadGenerator; use Composer\Package\Link; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Util\Filesystem; use Composer\Package\AliasPackage; use Composer\Package\Package; @@ -366,8 +367,8 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), )); $packages = array(); @@ -395,7 +396,7 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), + new Link('a', 'a/a', new MatchAllConstraint()), )); $packages = array(); @@ -403,11 +404,11 @@ class AutoloadGeneratorTest extends TestCase $packages[] = $b = new Package('b/b', '1.0', '1.0'); $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); $a->setRequires(array( - new Link('a/a', 'b/b'), + new Link('a/a', 'b/b', new MatchAllConstraint()), )); $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); $b->setRequires(array( - new Link('b/b', 'a/a'), + new Link('b/b', 'a/a', new MatchAllConstraint()), )); $this->repository->expects($this->once()) @@ -427,13 +428,13 @@ class AutoloadGeneratorTest extends TestCase public function testNonDevAutoloadShouldIncludeReplacedPackages() { $package = new Package('a', '1.0', '1.0'); - $package->setRequires(array(new Link('a', 'a/a'))); + $package->setRequires(array(new Link('a', 'a/a', new MatchAllConstraint()))); $packages = array(); $packages[] = $a = new Package('a/a', '1.0', '1.0'); $packages[] = $b = new Package('b/b', '1.0', '1.0'); - $a->setRequires(array(new Link('a/a', 'b/c'))); + $a->setRequires(array(new Link('a/a', 'b/c', new MatchAllConstraint()))); $b->setAutoload(array('psr-4' => array('B\\' => 'src/'))); $b->setReplaces( @@ -462,7 +463,7 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), + new Link('a', 'a/a', new MatchAllConstraint()), )); $packages = array(); @@ -470,11 +471,11 @@ class AutoloadGeneratorTest extends TestCase $packages[] = $b = new Package('b/b', '1.0', '1.0'); $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); $a->setRequires(array( - new Link('a/a', 'c/c'), + new Link('a/a', 'c/c', new MatchAllConstraint()), )); $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); $b->setReplaces(array( - new Link('b/b', 'c/c'), + new Link('b/b', 'c/c', new MatchAllConstraint()), )); $this->repository->expects($this->once()) @@ -495,7 +496,7 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a') + new Link('a', 'a/a', new MatchAllConstraint()) )); $packages = array(); @@ -506,18 +507,18 @@ class AutoloadGeneratorTest extends TestCase $packages[] = $e = new Package('e/e', '1.0', '1.0'); $a->setAutoload(array('classmap' => array('src/A.php'))); $a->setRequires(array( - new Link('a/a', 'b/b') + new Link('a/a', 'b/b', new MatchAllConstraint()) )); $b->setAutoload(array('classmap' => array('src/B.php'))); $b->setRequires(array( - new Link('b/b', 'e/e') + new Link('b/b', 'e/e', new MatchAllConstraint()) )); $c->setAutoload(array('classmap' => array('src/C.php'))); $c->setReplaces(array( - new Link('c/c', 'b/b') + new Link('c/c', 'b/b', new MatchAllConstraint()) )); $c->setRequires(array( - new Link('c/c', 'd/d') + new Link('c/c', 'd/d', new MatchAllConstraint()) )); $d->setAutoload(array('classmap' => array('src/D.php'))); $e->setAutoload(array('classmap' => array('src/E.php'))); @@ -547,7 +548,7 @@ class AutoloadGeneratorTest extends TestCase { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), + new Link('a', 'a/a', new MatchAllConstraint()), )); $package->setAutoload(array( @@ -652,8 +653,8 @@ EOF; { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), )); $packages = array(); @@ -692,8 +693,8 @@ EOF; { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), )); $packages = array(); @@ -732,9 +733,9 @@ EOF; { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), - new Link('a', 'c/c'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), + new Link('a', 'c/c', new MatchAllConstraint()), )); $packages = array(); @@ -777,9 +778,9 @@ EOF; { $package = new Package('a', '1.0', '1.0'); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), - new Link('a', 'c/c'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), + new Link('a', 'c/c', new MatchAllConstraint()), )); $packages = array(); @@ -827,9 +828,9 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array('files' => array('root.php'))); $package->setRequires(array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), - new Link('a', 'c/c'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), + new Link('a', 'c/c', new MatchAllConstraint()), )); $packages = array(); @@ -878,9 +879,9 @@ EOF; $notAutoloadPackage = new Package('a', '1.0', '1.0'); $requires = array( - new Link('a', 'a/a'), - new Link('a', 'b/b'), - new Link('a', 'c/c'), + new Link('a', 'a/a', new MatchAllConstraint()), + new Link('a', 'b/b', new MatchAllConstraint()), + new Link('a', 'c/c', new MatchAllConstraint()), ); $autoloadPackage->setRequires($requires); $notAutoloadPackage->setRequires($requires); @@ -949,10 +950,10 @@ EOF; $package = new Package('a', '1.0', '1.0'); $package->setAutoload(array('files' => array('root2.php'))); $package->setRequires(array( - new Link('a', 'z/foo'), - new Link('a', 'b/bar'), - new Link('a', 'd/d'), - new Link('a', 'e/e'), + new Link('a', 'z/foo', new MatchAllConstraint()), + new Link('a', 'b/bar', new MatchAllConstraint()), + new Link('a', 'd/d', new MatchAllConstraint()), + new Link('a', 'e/e', new MatchAllConstraint()), )); $packages = array(); @@ -963,18 +964,18 @@ EOF; $packages[] = $e = new Package('e/e', '1.0', '1.0'); $z->setAutoload(array('files' => array('testA.php'))); - $z->setRequires(array(new Link('z/foo', 'c/lorem'))); + $z->setRequires(array(new Link('z/foo', 'c/lorem', new MatchAllConstraint()))); $b->setAutoload(array('files' => array('testB.php'))); - $b->setRequires(array(new Link('b/bar', 'c/lorem'), new Link('b/bar', 'd/d'))); + $b->setRequires(array(new Link('b/bar', 'c/lorem', new MatchAllConstraint()), new Link('b/bar', 'd/d', new MatchAllConstraint()))); $c->setAutoload(array('files' => array('testC.php'))); $d->setAutoload(array('files' => array('testD.php'))); - $d->setRequires(array(new Link('d/d', 'c/lorem'))); + $d->setRequires(array(new Link('d/d', 'c/lorem', new MatchAllConstraint()))); $e->setAutoload(array('files' => array('testE.php'))); - $e->setRequires(array(new Link('e/e', 'c/lorem'))); + $e->setRequires(array(new Link('e/e', 'c/lorem', new MatchAllConstraint()))); $this->repository->expects($this->once()) ->method('getCanonicalPackages') @@ -1022,8 +1023,8 @@ EOF; 'classmap' => array($this->workingDir.'/src'), )); $mainPackage->setRequires(array( - new Link('z', 'a/a'), - new Link('z', 'b/b'), + new Link('z', 'a/a', new MatchAllConstraint()), + new Link('z', 'b/b', new MatchAllConstraint()), )); $packages = array(); @@ -1285,7 +1286,7 @@ EOF; 'files' => array('test.php'), )); $package->setRequires(array( - new Link('a', 'b/b'), + new Link('a', 'b/b', new MatchAllConstraint()), )); $vendorPackage = new Package('b/b', '1.0', '1.0'); diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-priority-conflicting.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-priority-conflicting.test new file mode 100644 index 000000000..5456f0baf --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-priority-conflicting.test @@ -0,0 +1,62 @@ +--TEST-- +Check root aliases are loaded + +--ROOT-- +{ + "minimum-stability": "dev", + "aliases": [ + { + "package": "req/pkg", + "version": "dev-feature-foo", + "alias": "dev-master" + } + ] +} + + +--REQUEST-- +{ + "require": { + "package/a": "dev-master", + "req/pkg": "dev-feature-foo" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + { + "name": "req/pkg", "version": "dev-feature-foo", + "source": { "reference": "feat.f", "type": "git", "url": "" } + }, + { + "name": "req/pkg", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } }, + "source": { "reference": "forked", "type": "git", "url": "" }, + "default-branch": true + }, + { + "name": "req/pkg", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "1.0.x-dev" } }, + "source": { "reference": "master", "type": "git", "url": "" }, + "default-branch": true + }, + { + "name": "package/a", "version": "dev-master", + "require": { "req/pkg": "dev-master" }, + "default-branch": true + } + ] +] + +--EXPECT-- +[ + "req/pkg-dev-feature-foo#feat.f", + "req/pkg-dev-master#feat.f (alias of dev-feature-foo)", + "package/a-dev-master", + "package/a-9999999-dev (alias of dev-master)" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-with-reference.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-with-reference.test new file mode 100644 index 000000000..f3cbca7f1 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/alias-with-reference.test @@ -0,0 +1,59 @@ +--TEST-- +Check root aliases get selected correctly + +--ROOT-- +{ + "stability-flags": { + "a/aliased": "dev" + }, + "aliases": [ + { + "package": "a/aliased", + "version": "dev-master", + "alias": "1.0.0" + } + ], + "references": { + "a/aliased": "abcd" + } +} + + +--REQUEST-- +{ + "require": { + "a/aliased": "dev-master", + "b/requirer": "*" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + { + "name": "a/aliased", "version": "dev-master", + "source": { "reference": "orig", "type": "git", "url": "" }, + "default-branch": true + }, + { + "name": "a/aliased", "version": "1.0" + }, + { + "name": "b/requirer", "version": "1.0.0", + "require": { "a/aliased": "1.0.0" }, + "source": { "reference": "1.0.0", "type": "git", "url": "" } + } + ] +] + +--EXPECT-- +[ + "a/aliased-dev-master#abcd", + "a/aliased-1.0.0.0#abcd (alias of dev-master)", + "b/requirer-1.0.0.0#1.0.0", + "a/aliased-9999999-dev#abcd (alias of dev-master)" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/constraint-expansion-works-with-exact-versions.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/constraint-expansion-works-with-exact-versions.test new file mode 100644 index 000000000..908b3602c --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/constraint-expansion-works-with-exact-versions.test @@ -0,0 +1,31 @@ +--TEST-- +Tests if version constraint is expanded. If not, dep/dep 3.0.0 would not be loaded here. + +--REQUEST-- +{ + "require": { + "root/req": "*" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.3.4"}}, + {"name": "dep/dep", "version": "2.3.4", "require": {"dep/dep2": "2.*"}}, + {"name": "dep/dep", "version": "2.3.5"}, + {"name": "dep/dep2", "version": "2.3.4", "require": {"dep/dep": "2.*"}} + ] +] + +--EXPECT-- +[ + "root/req-1.0.0.0", + "dep/dep-2.3.4.0", + "dep/dep2-2.3.4.0", + "dep/dep-2.3.5.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-do-not-load-from-repos.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-do-not-load-from-repos.test index f1dbd69fe..fb52a9b6d 100644 --- a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-do-not-load-from-repos.test +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-do-not-load-from-repos.test @@ -3,21 +3,34 @@ Fixed packages do not get loaded from the repos --REQUEST-- { - "some/pkg": "*" + "require": { + "some/pkg": "*", + "root/req": "*" + } } --FIXED-- [ - {"name": "some/pkg", "version": "1.0.3", "id": 1} + {"name": "some/pkg", "version": "1.0.3", "id": 1}, + {"name": "dep/dep", "version": "2.1.5", "id": 2} ] ---PACKAGES-- +--PACKAGE-REPOS-- [ - {"name": "some/pkg", "version": "1.0.0"}, - {"name": "some/pkg", "version": "1.1.0"} + [ + {"name": "some/pkg", "version": "1.0.0"}, + {"name": "some/pkg", "version": "1.1.0"}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "root/req", "version": "2.0.0", "require": {"dep/dep": "3.*"}}, + {"name": "dep/dep", "version": "2.3.4"}, + {"name": "dep/dep", "version": "3.0.1"} + ] ] --EXPECT-- [ - 1 + 1, + 2, + "root/req-1.0.0.0", + "root/req-2.0.0.0" ] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-replaced-do-not-load-from-repos.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-replaced-do-not-load-from-repos.test new file mode 100644 index 000000000..4847411ef --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/fixed-packages-replaced-do-not-load-from-repos.test @@ -0,0 +1,33 @@ +--TEST-- +Packages replaced by fixed packages do not get loaded from the repos + +--REQUEST-- +{ + "require": { + "some/pkg": "*", + "root/req": "*" + } +} + +--FIXED-- +[ + {"name": "some/pkg", "version": "1.0.3", "replace": {"dep/dep": "2.1.0"}, "id": 1} +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "some/pkg", "version": "1.0.0"}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "root/req", "version": "2.0.0", "require": {"dep/dep": "3.*"}}, + {"name": "dep/dep", "version": "2.3.4"}, + {"name": "dep/dep", "version": "3.0.1"} + ] +] + +--EXPECT-- +[ + 1, + "root/req-1.0.0.0", + "root/req-2.0.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update.test new file mode 100644 index 000000000..01eda5215 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace-partial-update.test @@ -0,0 +1,107 @@ +--TEST-- +Check that replacers from additional repositories are loaded when doing a partial update + +--REQUEST-- +{ + "require": { + "base/package": "^1.0", + "indirect/replacer": "^1.0" + }, + "locked": [ + {"name": "shared/dep", "version": "1.2.0", "id": 1}, + {"name": "indirect/replacer", "version": "1.2.0", "require": {"replacer/package": "^1.2"}, "id": 2}, + {"name": "replacer/package", "version": "1.2.0", "require": {"shared/dep": "^1.1"}, "replace": {"base/package": "1.2.0"}, "id": 3} + ], + "allowList": [ + "indirect/replacer" + ], + "allowTransitiveDepsNoRootRequire": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + { + "name": "base/package", + "version": "1.0.0", + "require": { + "shared/dep": "^1.2" + } + }, + { + "name": "shared/dep", + "version": "1.0.0" + }, + { + "name": "shared/dep", + "version": "1.2.0" + } + ], + [ + { + "name": "base/package", + "version": "1.1.0" + }, + { + "name": "shared/dep", + "version": "1.3.0" + } + ], + { + "canonical": false, + "packages": [ + { + "name": "indirect/replacer", + "version": "1.2.0", + "require": { + "replacer/package": "^1.2" + } + }, + { + "name": "replacer/package", + "version": "1.2.0", + "require": { + "shared/dep": "^1.1" + } + }, + { + "name": "shared/dep", + "version": "1.1.0" + } + ] + }, + [ + { + "name": "replacer/package", + "version": "1.0.0", + "require": { + "shared/dep": "^1.0" + }, + "replace": { + "base/package": "1.0.0" + } + }, + { + "name": "indirect/replacer", + "version": "1.0.0", + "require": { + "replacer/package": "^1.0" + } + } + ] +] + +--EXPECT-- +[ + "indirect/replacer-1.2.0.0", + "indirect/replacer-1.0.0.0", + "replacer/package-1.2.0.0", + "replacer/package-1.0.0.0", + "base/package-1.0.0.0", + "shared/dep-1.0.0.0", + "shared/dep-1.2.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace.test new file mode 100644 index 000000000..00c63e4f5 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/multi-repo-replace.test @@ -0,0 +1,98 @@ +--TEST-- +Check that replacers from additional repositories are loaded + +--REQUEST-- +{ + "require": { + "base/package": "^1.0", + "indirect/replacer": "^1.0" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + { + "name": "base/package", + "version": "1.0.0", + "require": { + "shared/dep": "^1.2" + } + }, + { + "name": "shared/dep", + "version": "1.0.0" + }, + { + "name": "shared/dep", + "version": "1.2.0" + } + ], + [ + { + "name": "base/package", + "version": "1.1.0" + }, + { + "name": "shared/dep", + "version": "1.3.0" + } + ], + { + "canonical": false, + "packages": [ + { + "name": "indirect/replacer", + "version": "1.2.0", + "require": { + "replacer/package": "^1.2" + } + }, + { + "name": "replacer/package", + "version": "1.2.0", + "require": { + "shared/dep": "^1.1" + } + }, + { + "name": "shared/dep", + "version": "1.1.0" + } + ] + }, + [ + { + "name": "replacer/package", + "version": "1.0.0", + "require": { + "shared/dep": "^1.0" + }, + "replace": { + "base/package": "1.2.0" + } + }, + { + "name": "indirect/replacer", + "version": "1.0.0", + "require": { + "replacer/package": "^1.0" + } + } + ] +] + +--EXPECT-- +[ + "base/package-1.0.0.0", + "indirect/replacer-1.2.0.0", + "indirect/replacer-1.0.0.0", + "shared/dep-1.2.0.0", + "replacer/package-1.2.0.0", + "replacer/package-1.0.0.0", + "shared/dep-1.0.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/must-expand-root-reqs.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/must-expand-root-reqs.test new file mode 100644 index 000000000..fb52a9b6d --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/must-expand-root-reqs.test @@ -0,0 +1,36 @@ +--TEST-- +Fixed packages do not get loaded from the repos + +--REQUEST-- +{ + "require": { + "some/pkg": "*", + "root/req": "*" + } +} + +--FIXED-- +[ + {"name": "some/pkg", "version": "1.0.3", "id": 1}, + {"name": "dep/dep", "version": "2.1.5", "id": 2} +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "some/pkg", "version": "1.0.0"}, + {"name": "some/pkg", "version": "1.1.0"}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "root/req", "version": "2.0.0", "require": {"dep/dep": "3.*"}}, + {"name": "dep/dep", "version": "2.3.4"}, + {"name": "dep/dep", "version": "3.0.1"} + ] +] + +--EXPECT-- +[ + 1, + 2, + "root/req-1.0.0.0", + "root/req-2.0.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-expansion.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-expansion.test new file mode 100644 index 000000000..a41f4d1aa --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-expansion.test @@ -0,0 +1,34 @@ +--TEST-- +Tests if version constraint is expanded. If not, dep/dep 3.0.0 would not be loaded here. + +--REQUEST-- +{ + "require": { + "root/req": "*" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "dep/dep", "version": "2.3.4", "require": {"dep/dep2": "2.*"}}, + {"name": "dep/dep", "version": "2.3.5"}, + {"name": "dep/dep", "version": "3.0.0"}, + {"name": "dep/dep", "version": "4.0.0"}, + {"name": "dep/dep2", "version": "2.3.4", "require": {"dep/dep": "3.*"}} + ] +] + +--EXPECT-- +[ + "root/req-1.0.0.0", + "dep/dep-2.3.4.0", + "dep/dep-2.3.5.0", + "dep/dep2-2.3.4.0", + "dep/dep-3.0.0.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-recursive.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-recursive.test new file mode 100644 index 000000000..0eb8bbb69 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/package-versions-are-not-loaded-if-not-required-recursive.test @@ -0,0 +1,31 @@ +--TEST-- +Test irrelevant package versions are not loaded recursively + +--REQUEST-- +{ + "require": { + "root/req": "*" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "dep/dep", "version": "2.3.4", "require": {"dep/dep2": "2.*"}}, + {"name": "dep/dep", "version": "3.0.1"}, + {"name": "dep/dep2", "version": "2.3.4"}, + {"name": "dep/dep2", "version": "3.0.1"} + ] +] + +--EXPECT-- +[ + "root/req-1.0.0.0", + "dep/dep-2.3.4.0", + "dep/dep2-2.3.4.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/packages-that-do-not-exist.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/packages-that-do-not-exist.test new file mode 100644 index 000000000..bcf041002 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/packages-that-do-not-exist.test @@ -0,0 +1,27 @@ +--TEST-- +Test package is not found + +--REQUEST-- +{ + "require": { + "root/req": "*" + } +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "dep/dep", "version": "2.3.4", "require": {"dep/dep3": "2.*"}} + ] +] + +--EXPECT-- +[ + "root/req-1.0.0.0", + "dep/dep-2.3.4.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-no-root-unfix.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-no-root-unfix.test new file mode 100644 index 000000000..4f3f3c8c0 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-no-root-unfix.test @@ -0,0 +1,49 @@ +--TEST-- +Partially updating one root requirement with transitive deps without root requirements keeps the other root requirement fixed. + +--REQUEST-- +{ + "require": { + "root/update": "*", + "root/fix": "*" + }, + "locked": [ + {"name": "root/update", "version": "1.0.1", "require": {"dep/dep": "1.*", "dep2/dep2": "1.*"}, "id": 1}, + {"name": "dep/dep", "version": "1.0.2", "require": {"root/fix": "1.*"}, "id": 2}, + {"name": "dep2/dep2", "version": "1.0.2", "require": {"dep/dep": "1.*"}, "id": 3}, + {"name": "root/fix", "version": "1.0.3", "id": 4} + ], + "allowList": [ + "root/update" + ], + "allowTransitiveDepsNoRootRequire": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/update", "version": "1.0.4", "require": {"dep/dep": "1.*", "dep2/dep2": "1.*"}}, + {"name": "root/update", "version": "1.0.5", "require": {"dep/dep": "2.*", "dep2/dep2": "2.*"}}, + {"name": "root/fix", "version": "1.0.6"}, + {"name": "root/fix", "version": "2.0.7"}, + {"name": "dep/dep", "version": "1.0.8", "require": {"root/fix": "1.*"}}, + {"name": "dep/dep", "version": "2.0.9", "require": {"root/fix": "2.*"}}, + {"name": "dep2/dep2", "version": "1.0.8", "require": {"dep/dep": "1.*"}}, + {"name": "dep2/dep2", "version": "2.0.9", "require": {"dep/dep": "2.*"}} + ] +] + +--EXPECT-- +[ + 4, + "root/update-1.0.4.0", + "root/update-1.0.5.0", + "dep/dep-1.0.8.0", + "dep/dep-2.0.9.0", + "dep2/dep2-1.0.8.0", + "dep2/dep2-2.0.9.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-unfix.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-unfix.test new file mode 100644 index 000000000..2db47d1bd --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-transitive-deps-unfix.test @@ -0,0 +1,50 @@ +--TEST-- +Partially updating one root requirement with transitive deps fully updates another one too. + +--REQUEST-- +{ + "require": { + "root/update": "*", + "root/dep": "1.0.*", + "root/dep2": "*" + }, + "locked": [ + {"name": "root/update", "version": "1.0.1", "require": {"dep/dep": "1.*"}}, + {"name": "dep/dep", "version": "1.0.2", "require": {"root/dep": "1.*", "root/dep2": "1.*"}}, + {"name": "root/dep", "version": "1.0.3"}, + {"name": "root/dep2", "version": "1.0.3"} + ], + "allowList": [ + "root/update" + ], + "allowTransitiveDeps": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/update", "version": "1.0.4", "require": {"dep/dep": "1.*"}}, + {"name": "root/update", "version": "1.0.5", "require": {"dep/dep": "2.*"}}, + {"name": "root/dep", "version": "1.0.6"}, + {"name": "root/dep", "version": "2.0.7"}, + {"name": "root/dep2", "version": "1.0.6"}, + {"name": "root/dep2", "version": "2.0.7"}, + {"name": "dep/dep", "version": "1.0.8", "require": {"root/dep": "1.*", "root/dep2": "1.*"}}, + {"name": "dep/dep", "version": "2.0.9", "require": {"root/dep": "2.*", "root/dep2": "2.*"}} + ] +] + +--EXPECT-- +[ + "root/update-1.0.4.0", + "root/update-1.0.5.0", + "dep/dep-1.0.8.0", + "dep/dep-2.0.9.0", + "root/dep-1.0.6.0", + "root/dep2-1.0.6.0", + "root/dep2-2.0.7.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-with-replacers.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-with-replacers.test new file mode 100644 index 000000000..c4148d5ba --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update-unfixing-with-replacers.test @@ -0,0 +1,52 @@ +--TEST-- +Fixed packages and replacers get unfixed correctly (refs https://github.com/composer/composer/pull/8942) + +--REQUEST-- +{ + "require": { + "root/req1": "*", + "root/req3": "*" + }, + "locked": [ + {"name": "root/req1", "version": "1.0.0", "require": {"replacer/pkg": "1.*"}}, + {"name": "root/req3", "version": "1.0.0", "require": {"replaced/pkg": "1.*", "dep/dep": "2.*"}}, + {"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "*"}}, + {"name": "dep/dep", "version": "2.3.5"} + ], + "allowList": [ + "root/req1" + ], + "allowTransitiveDeps": true +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "root/req1", "version": "1.0.0", "require": {"replacer/pkg": "1.*"}}, + {"name": "root/req1", "version": "1.1.0", "require": {"replacer/pkg": "1.*"}}, + {"name": "root/req3", "version": "1.0.0", "require": {"replaced/pkg": "1.*"}}, + {"name": "root/req3", "version": "1.1.0", "require": {"replaced/pkg": "1.*"}}, + {"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "*"}}, + {"name": "replacer/pkg", "version": "1.1.0", "replace": {"replaced/pkg": "*"}}, + {"name": "replaced/pkg", "version": "1.2.3"}, + {"name": "replaced/pkg", "version": "1.2.4"}, + {"name": "dep/dep", "version": "2.3.5"}, + {"name": "dep/dep", "version": "2.3.6"} + ] +] + +--EXPECT-- +[ + "root/req3-1.0.0.0 (locked)", + "dep/dep-2.3.5.0 (locked)", + "root/req1-1.0.0.0", + "root/req1-1.1.0.0", + "replacer/pkg-1.0.0.0", + "replacer/pkg-1.1.0.0", + "replaced/pkg-1.2.3.0", + "replaced/pkg-1.2.4.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update.test new file mode 100644 index 000000000..a4d04ced5 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/partial-update.test @@ -0,0 +1,39 @@ +--TEST-- +Partially updating a root requirement without deps, still selects a new dependency if the update results in a replacement missing for another locked package. + +--REQUEST-- +{ + "require": { + "some/pkg": "*", + "root/req": "*" + }, + "locked": [ + {"name": "some/pkg", "version": "1.0.3", "replace": {"dep/dep": "2.1.0"}, "id": 1}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}, "id": 2} + ], + "allowList": [ + "some/pkg" + ] +} + +--FIXED-- +[ +] + +--PACKAGE-REPOS-- +[ + [ + {"name": "some/pkg", "version": "1.0.4"}, + {"name": "root/req", "version": "1.0.0", "require": {"dep/dep": "2.*"}}, + {"name": "root/req", "version": "2.0.0", "require": {"dep/dep": "3.*"}}, + {"name": "dep/dep", "version": "2.3.4"}, + {"name": "dep/dep", "version": "3.0.1"} + ] +] + +--EXPECT-- +[ + 2, + "some/pkg-1.0.4.0", + "dep/dep-2.3.4.0" +] diff --git a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/stability-flags-take-over-minimum-stability-and-filter-packages.test b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/stability-flags-take-over-minimum-stability-and-filter-packages.test index 5a6ca8f2f..849033dcb 100644 --- a/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/stability-flags-take-over-minimum-stability-and-filter-packages.test +++ b/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/stability-flags-take-over-minimum-stability-and-filter-packages.test @@ -18,19 +18,23 @@ Stability flags apply --REQUEST-- { - "flagged/pkg": "*", - "default/pkg": "*" + "require": { + "flagged/pkg": "*", + "default/pkg": "*" + } } ---PACKAGES-- +--PACKAGE-REPOS-- [ - {"name": "flagged/pkg", "version": "1.0.0", "id": 1}, - {"name": "flagged/pkg", "version": "1.0.0-beta", "id": 2}, - {"name": "flagged/pkg", "version": "1.0.0-dev", "id": 3}, - {"name": "flagged/pkg", "version": "1.0.0-RC", "id": 4}, - {"name": "default/pkg", "version": "1.0.0", "id": 5}, - {"name": "default/pkg", "version": "1.0.0-RC", "id": 6}, - {"name": "default/pkg", "version": "1.0.0-alpha", "id": 7} + [ + {"name": "flagged/pkg", "version": "1.0.0", "id": 1}, + {"name": "flagged/pkg", "version": "1.0.0-beta", "id": 2}, + {"name": "flagged/pkg", "version": "1.0.0-dev", "id": 3}, + {"name": "flagged/pkg", "version": "1.0.0-RC", "id": 4}, + {"name": "default/pkg", "version": "1.0.0", "id": 5}, + {"name": "default/pkg", "version": "1.0.0-RC", "id": 6}, + {"name": "default/pkg", "version": "1.0.0-alpha", "id": 7} + ] ] --EXPECT-- @@ -40,5 +44,5 @@ Stability flags apply 4, 5, 6, - "default/pkg-1.2.0.0 alias of 6" + "default/pkg-1.2.0.0 (alias of 6)" ] diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index 647ee2c2d..a72e42c96 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\DependencyResolver; use Composer\IO\NullIO; use Composer\Repository\ArrayRepository; +use Composer\Repository\FilterRepository; use Composer\Repository\LockArrayRepository; use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\Pool; @@ -36,11 +37,12 @@ class PoolBuilderTest extends TestCase /** * @dataProvider getIntegrationTests */ - public function testPoolBuilder($file, $message, $expect, $root, $requestData, $packages, $fixed) + public function testPoolBuilder($file, $message, $expect, $root, $requestData, $packageRepos, $fixed) { $rootAliases = !empty($root['aliases']) ? $root['aliases'] : array(); $minimumStability = !empty($root['minimum-stability']) ? $root['minimum-stability'] : 'stable'; $stabilityFlags = !empty($root['stability-flags']) ? $root['stability-flags'] : array(); + $rootReferences = !empty($root['references']) ? $root['references'] : array(); $stabilityFlags = array_map(function ($stability) { return BasePackage::$stabilities[$stability]; }, $stabilityFlags); @@ -71,16 +73,42 @@ class PoolBuilderTest extends TestCase return $pkg; }; - $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases); - $repositorySet->addRepository($repo = new ArrayRepository()); - foreach ($packages as $package) { - $repo->addPackage($loadPackage($package)); + $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $rootReferences); + foreach ($packageRepos as $packages) { + $repo = new ArrayRepository(); + if (isset($packages['canonical']) || isset($packages['only']) || isset($packages['exclude'])) { + $options = $packages; + $packages = $options['packages']; + unset($options['packages']); + $repositorySet->addRepository(new FilterRepository($repo, $options)); + } else { + $repositorySet->addRepository($repo); + } + foreach ($packages as $package) { + $repo->addPackage($loadPackage($package)); + } } + $repositorySet->addRepository($lockedRepo = new LockArrayRepository()); - $request = new Request(); - foreach ($requestData as $package => $constraint) { + if (isset($requestData['locked'])) { + foreach ($requestData['locked'] as $package) { + $lockedRepo->addPackage($loadPackage($package)); + } + } + $request = new Request($lockedRepo); + foreach ($requestData['require'] as $package => $constraint) { $request->requireName($package, $parser->parseConstraints($constraint)); } + if (isset($requestData['allowList'])) { + $transitiveDeps = Request::UPDATE_ONLY_LISTED; + if (isset($requestData['allowTransitiveDepsNoRootRequire']) && $requestData['allowTransitiveDepsNoRootRequire']) { + $transitiveDeps = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE; + } + if (isset($requestData['allowTransitiveDeps']) && $requestData['allowTransitiveDeps']) { + $transitiveDeps = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS; + } + $request->setUpdateAllowList(array_flip($requestData['allowList']), $transitiveDeps); + } foreach ($fixed as $fixedPackage) { $request->fixPackage($loadPackage($fixedPackage)); @@ -97,11 +125,23 @@ class PoolBuilderTest extends TestCase return $id; } - if ($package instanceof AliasPackage && $id = array_search($package->getAliasOf(), $packageIds, true)) { - return (string) $package->getName().'-'.$package->getVersion() .' alias of '.$id; + $suffix = ''; + if ($package->getSourceReference()) { + $suffix = '#'.$package->getSourceReference(); + } + if ($package->getRepository() instanceof LockArrayRepository) { + $suffix .= ' (locked)'; + } + + if ($package instanceof AliasPackage) { + if ($id = array_search($package->getAliasOf(), $packageIds, true)) { + return (string) $package->getName().'-'.$package->getVersion() . $suffix . ' (alias of '.$id . ')'; + } + + return (string) $package->getName().'-'.$package->getVersion() . $suffix . ' (alias of '.$package->getAliasOf()->getVersion().')'; } - return (string) $package; + return (string) $package->getName().'-'.$package->getVersion() . $suffix; }, $result); $this->assertSame($expect, $result); @@ -124,7 +164,7 @@ class PoolBuilderTest extends TestCase $request = JsonFile::parseJson($testData['REQUEST']); $root = !empty($testData['ROOT']) ? JsonFile::parseJson($testData['ROOT']) : array(); - $packages = JsonFile::parseJson($testData['PACKAGES']); + $packageRepos = JsonFile::parseJson($testData['PACKAGE-REPOS']); $fixed = array(); if (!empty($testData['FIXED'])) { $fixed = JsonFile::parseJson($testData['FIXED']); @@ -134,7 +174,7 @@ class PoolBuilderTest extends TestCase die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file))); } - $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $root, $request, $packages, $fixed); + $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $root, $request, $packageRepos, $fixed); } return $tests; @@ -149,7 +189,7 @@ class PoolBuilderTest extends TestCase 'ROOT' => false, 'REQUEST' => true, 'FIXED' => false, - 'PACKAGES' => true, + 'PACKAGE-REPOS' => true, 'EXPECT' => true, ); diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index e2a3ce9e6..a93bc404b 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Request; use Composer\Repository\ArrayRepository; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Test\TestCase; class RequestTest extends TestCase @@ -34,7 +35,7 @@ class RequestTest extends TestCase $this->assertEquals( array( - 'foo' => null, + 'foo' => new MatchAllConstraint(), ), $request->getRequires() ); diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php index 2e1a9921d..ebf15fcac 100644 --- a/tests/Composer/Test/DependencyResolver/RuleTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -19,6 +19,7 @@ use Composer\DependencyResolver\Pool; use Composer\Package\BasePackage; use Composer\Package\Link; use Composer\Repository\ArrayRepository; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Test\TestCase; class RuleTest extends TestCase @@ -102,8 +103,11 @@ class RuleTest extends TestCase $repositorySetMock = $this->getMockBuilder('Composer\Repository\RepositorySet')->disableOriginalConstructor()->getMock(); $requestMock = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock(); - $rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_PACKAGE_REQUIRES, new Link('baz', 'foo')); + $emptyConstraint = new MatchAllConstraint(); + $emptyConstraint->setPrettyString('*'); - $this->assertEquals('baz 1.1 relates to foo -> satisfiable by foo[2.1].', $rule->getPrettyString($repositorySetMock, $requestMock, $pool, false)); + $rule = new GenericRule(array($p1->getId(), -$p2->getId()), Rule::RULE_PACKAGE_REQUIRES, new Link('baz', 'foo', $emptyConstraint)); + + $this->assertEquals('baz 1.1 relates to foo * -> satisfiable by foo[2.1].', $rule->getPrettyString($repositorySetMock, $requestMock, $pool, false)); } } diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 6fa7f1f84..6ed4c6bbd 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -235,8 +235,8 @@ class SolverTest extends TestCase $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1')); $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); - $packageA->setRequires(array('b' => new Link('A', 'B', null, 'requires'))); - $newPackageA->setRequires(array('b' => new Link('A', 'B', null, 'requires'))); + $packageA->setRequires(array('b' => new Link('A', 'B', new MatchAllConstraint(), 'requires'))); + $newPackageA->setRequires(array('b' => new Link('A', 'B', new MatchAllConstraint(), 'requires'))); $this->reposComplete(); @@ -639,8 +639,11 @@ class SolverTest extends TestCase $this->reposComplete(); - $this->request->requireName('A'); - $this->request->requireName('B'); + $emptyConstraint = new MatchAllConstraint(); + $emptyConstraint->setPrettyString('*'); + + $this->request->requireName('A', $emptyConstraint); + $this->request->requireName('B', $emptyConstraint); $this->createSolver(); try { @@ -652,9 +655,9 @@ class SolverTest extends TestCase $msg = "\n"; $msg .= " Problem 1\n"; - $msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n"; + $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; $msg .= " - A 1.0 conflicts with B 1.0.\n"; - $msg .= " - Root composer.json requires b -> satisfiable by B[1.0].\n"; + $msg .= " - Root composer.json requires b * -> satisfiable by B[1.0].\n"; $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } @@ -683,7 +686,7 @@ class SolverTest extends TestCase $msg = "\n"; $msg .= " Problem 1\n"; - $msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n"; + $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; $msg .= " - A 1.0 requires b >= 2.0 -> found B[1.0] but it does not match the constraint.\n"; $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } @@ -712,7 +715,10 @@ class SolverTest extends TestCase $this->reposComplete(); - $this->request->requireName('A'); + $emptyConstraint = new MatchAllConstraint(); + $emptyConstraint->setPrettyString('*'); + + $this->request->requireName('A', $emptyConstraint); $this->createSolver(); try { @@ -729,7 +735,7 @@ class SolverTest extends TestCase $msg .= " - B 1.0 requires c >= 1.0 -> satisfiable by C[1.0].\n"; $msg .= " - You can only install one version of a package, so only one of these can be installed: B[0.9, 1.0].\n"; $msg .= " - A 1.0 requires b >= 1.0 -> satisfiable by B[1.0].\n"; - $msg .= " - Root composer.json requires a -> satisfiable by A[1.0].\n"; + $msg .= " - Root composer.json requires a * -> satisfiable by A[1.0].\n"; $this->assertEquals($msg, $e->getPrettyString($this->repoSet, $this->request, $this->pool, false)); } } diff --git a/tests/Composer/Test/Package/RootAliasPackageTest.php b/tests/Composer/Test/Package/RootAliasPackageTest.php index a5fe9172d..459f935e5 100644 --- a/tests/Composer/Test/Package/RootAliasPackageTest.php +++ b/tests/Composer/Test/Package/RootAliasPackageTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Package; use Composer\Package\Link; use Composer\Package\RootAliasPackage; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Test\TestCase; use Prophecy\Argument; @@ -26,7 +27,7 @@ class RootAliasPackageTest extends TestCase $alias = new RootAliasPackage($root->reveal(), '1.0', '1.0.0.0'); $this->assertEmpty($alias->getRequires()); - $links = array(new Link('a', 'b', null, 'foo', 'self.version')); + $links = array(new Link('a', 'b', new MatchAllConstraint(), 'foo', 'self.version')); $alias->setRequires($links); $this->assertNotEmpty($alias->getRequires()); } @@ -38,7 +39,7 @@ class RootAliasPackageTest extends TestCase $alias = new RootAliasPackage($root->reveal(), '1.0', '1.0.0.0'); $this->assertEmpty($alias->getDevRequires()); - $links = array(new Link('a', 'b', null, 'foo', 'self.version')); + $links = array(new Link('a', 'b', new MatchAllConstraint(), 'foo', 'self.version')); $alias->setDevRequires($links); $this->assertNotEmpty($alias->getDevRequires()); } @@ -50,7 +51,7 @@ class RootAliasPackageTest extends TestCase $alias = new RootAliasPackage($root->reveal(), '1.0', '1.0.0.0'); $this->assertEmpty($alias->getConflicts()); - $links = array(new Link('a', 'b', null, 'foo', 'self.version')); + $links = array(new Link('a', 'b', new MatchAllConstraint(), 'foo', 'self.version')); $alias->setConflicts($links); $this->assertNotEmpty($alias->getConflicts()); } @@ -62,7 +63,7 @@ class RootAliasPackageTest extends TestCase $alias = new RootAliasPackage($root->reveal(), '1.0', '1.0.0.0'); $this->assertEmpty($alias->getProvides()); - $links = array(new Link('a', 'b', null, 'foo', 'self.version')); + $links = array(new Link('a', 'b', new MatchAllConstraint(), 'foo', 'self.version')); $alias->setProvides($links); $this->assertNotEmpty($alias->getProvides()); } @@ -74,7 +75,7 @@ class RootAliasPackageTest extends TestCase $alias = new RootAliasPackage($root->reveal(), '1.0', '1.0.0.0'); $this->assertEmpty($alias->getReplaces()); - $links = array(new Link('a', 'b', null, 'foo', 'self.version')); + $links = array(new Link('a', 'b', new MatchAllConstraint(), 'foo', 'self.version')); $alias->setReplaces($links); $this->assertNotEmpty($alias->getReplaces()); } diff --git a/tests/Composer/Test/Repository/InstalledRepositoryTest.php b/tests/Composer/Test/Repository/InstalledRepositoryTest.php index a37adb058..505278c3b 100644 --- a/tests/Composer/Test/Repository/InstalledRepositoryTest.php +++ b/tests/Composer/Test/Repository/InstalledRepositoryTest.php @@ -16,6 +16,7 @@ use Composer\Repository\InstalledRepository; use Composer\Repository\ArrayRepository; use Composer\Repository\InstalledArrayRepository; use Composer\Package\Link; +use Composer\Semver\Constraint\MatchAllConstraint; use Composer\Test\TestCase; class InstalledRepositoryTest extends TestCase @@ -30,8 +31,8 @@ class InstalledRepositoryTest extends TestCase $arrayRepoTwo->addPackage($bar = $this->getPackage('bar', '1')); $arrayRepoTwo->addPackage($bar2 = $this->getPackage('bar', '2')); - $foo->setReplaces(array(new Link('foo', 'provided'))); - $bar2->setProvides(array(new Link('bar', 'provided'))); + $foo->setReplaces(array(new Link('foo', 'provided', new MatchAllConstraint()))); + $bar2->setProvides(array(new Link('bar', 'provided', new MatchAllConstraint()))); $repo = new InstalledRepository(array($arrayRepoOne, $arrayRepoTwo)); diff --git a/tests/Composer/Test/Util/PackageSorterTest.php b/tests/Composer/Test/Util/PackageSorterTest.php index e93503436..8704a3af0 100644 --- a/tests/Composer/Test/Util/PackageSorterTest.php +++ b/tests/Composer/Test/Util/PackageSorterTest.php @@ -16,6 +16,7 @@ use Composer\Package\Link; use Composer\Package\Package; use Composer\Test\TestCase; use Composer\Util\PackageSorter; +use Composer\Semver\Constraint\MatchAllConstraint; class PackageSorterTest extends TestCase { @@ -120,7 +121,7 @@ class PackageSorterTest extends TestCase $links = array(); foreach ($requires as $requireName) { - $links[] = new Link($package->getName(), $requireName); + $links[] = new Link($package->getName(), $requireName, new MatchAllConstraint); } $package->setRequires($links);