Merge pull request #8850 from Toflar/filter-packages

Filter dependent packages early
main
Nils Adermann 4 years ago committed by GitHub
commit 046c54fdb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 <naderman@naderman.de>
@ -57,16 +58,22 @@ class PoolBuilder
* @var IOInterface
*/
private $io;
/**
* @psalm-var array<string, AliasPackage>
*/
private $aliasMap = array();
/**
* @psalm-var array<string, ConstraintInterface[]|null>
* @psalm-var array<string, ConstraintInterface>
*/
private $packagesToLoad = array();
/**
* @psalm-var array<string, ConstraintInterface>
*/
private $loadedPackages = array();
/**
* @psalm-var array<int, array<string, array<string, PackageInterface>>>
*/
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<string, bool>
*/
private $updateAllowWarned = array();
private $indexCounter = 0;
/**
* @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
* @psalm-param array<string, BasePackage::STABILITY_*> $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('<warning>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.</warning>');
}
}
$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('<warning>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.</warning>');
}
} 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('<warning>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.</warning>');
@ -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)]);
}
}
}

@ -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 <naderman@naderman.de>
@ -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;
}

@ -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()
{

@ -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;

@ -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;
}

@ -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'];
}

@ -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();
}

@ -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);
}
}

@ -75,11 +75,13 @@ interface RepositoryInterface extends \Countable
* @psalm-param array<string, BasePackage::STABILITY_*> $acceptableStabilities
* @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value
* @psalm-param array<string, BasePackage::STABILITY_*> $stabilityFlags
* @param array[] $alreadyLoaded an array of package name => package version => package
* @psalm-param array<string, array<string, PackageInterface>> $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

@ -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');

@ -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)"
]

@ -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)"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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"
]

@ -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)"
]

@ -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,
);

@ -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()
);

@ -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));
}
}

@ -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));
}
}

@ -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());
}

@ -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));

@ -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);

Loading…
Cancel
Save