commit
4589b9bb18
|
@ -11,7 +11,6 @@ on:
|
|||
env:
|
||||
COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
|
||||
COMPOSER_UPDATE_FLAGS: ""
|
||||
COMPOSER_TESTS_ARE_RUNNING: "1"
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
|
|
@ -353,3 +353,27 @@ See also https://github.com/composer/composer/issues/4180 for more information.
|
|||
Composer can unpack zipballs using either a system-provided `unzip` or `7z` (7-Zip) utility, or PHP's
|
||||
native `ZipArchive` class. On OSes where ZIP files can contain permissions and symlinks, we recommend
|
||||
installing `unzip` or `7z` as these features are not supported by `ZipArchive`.
|
||||
|
||||
|
||||
## Disabling the pool optimizer
|
||||
|
||||
In Composer, the `Pool` class contains all the packages that are relevant for the dependency
|
||||
resolving process. That is what is used to generate all the rules which are then
|
||||
passed on to the dependency solver.
|
||||
In order to improve performance, Composer tries to optimize this `Pool` by removing useless
|
||||
package information early on.
|
||||
|
||||
If all goes well, you should never notice any issues with it but in case you run into
|
||||
an unexpected result such as an unresolvable set of dependencies or conflicts where you
|
||||
think Composer is wrong, you might want to disable the optimizer by using the environment
|
||||
variable `COMPOSER_POOL_OPTIMIZER` and run the update again like so:
|
||||
|
||||
```bash
|
||||
COMPOSER_POOL_OPTIMIZER=0 php composer.phar update
|
||||
```
|
||||
|
||||
Now double check if the result is still the same. It will take significantly longer and use
|
||||
a lot more memory to run the dependency resolving process.
|
||||
|
||||
If the result is different, you likely hit a problem in the pool optimizer.
|
||||
Please [report this issue](https://github.com/composer/composer/issues) so it can be fixed.
|
||||
|
|
|
@ -36,16 +36,57 @@ class Pool implements \Countable
|
|||
protected $providerCache = array();
|
||||
/** @var BasePackage[] */
|
||||
protected $unacceptableFixedOrLockedPackages;
|
||||
/** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
|
||||
protected $removedVersions = array();
|
||||
/** @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version */
|
||||
protected $removedVersionsByPackage = array();
|
||||
|
||||
/**
|
||||
* @param BasePackage[] $packages
|
||||
* @param BasePackage[] $unacceptableFixedOrLockedPackages
|
||||
* @param array<string, array<string, string>> $removedVersions
|
||||
* @param array<string, array<string, string>> $removedVersionsByPackage
|
||||
*/
|
||||
public function __construct(array $packages = array(), array $unacceptableFixedOrLockedPackages = array())
|
||||
public function __construct(array $packages = array(), array $unacceptableFixedOrLockedPackages = array(), array $removedVersions = array(), array $removedVersionsByPackage = array())
|
||||
{
|
||||
$this->versionParser = new VersionParser;
|
||||
$this->setPackages($packages);
|
||||
$this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages;
|
||||
$this->removedVersions = $removedVersions;
|
||||
$this->removedVersionsByPackage = $removedVersionsByPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getRemovedVersions($name, ConstraintInterface $constraint)
|
||||
{
|
||||
if (!isset($this->removedVersions[$name])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ($this->removedVersions[$name] as $version => $prettyVersion) {
|
||||
if ($constraint->matches(new Constraint('==', $version))) {
|
||||
$result[$version] = $prettyVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $objectHash
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getRemovedVersionsByPackage($objectHash)
|
||||
{
|
||||
if (!isset($this->removedVersionsByPackage[$objectHash])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $this->removedVersionsByPackage[$objectHash];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,6 +262,14 @@ class Pool implements \Countable
|
|||
return \in_array($package, $this->unacceptableFixedOrLockedPackages, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BasePackage[]
|
||||
*/
|
||||
public function getUnacceptableFixedOrLockedPackages()
|
||||
{
|
||||
return $this->unacceptableFixedOrLockedPackages;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$str = "Pool:\n";
|
||||
|
|
|
@ -61,6 +61,10 @@ class PoolBuilder
|
|||
* @var ?EventDispatcher
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
/**
|
||||
* @var PoolOptimizer|null
|
||||
*/
|
||||
private $poolOptimizer;
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
|
@ -128,13 +132,14 @@ class PoolBuilder
|
|||
* @param string[] $rootReferences an array of package name => source reference
|
||||
* @phpstan-param array<string, string> $rootReferences
|
||||
*/
|
||||
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, EventDispatcher $eventDispatcher = null)
|
||||
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, EventDispatcher $eventDispatcher = null, PoolOptimizer $poolOptimizer = null)
|
||||
{
|
||||
$this->acceptableStabilities = $acceptableStabilities;
|
||||
$this->stabilityFlags = $stabilityFlags;
|
||||
$this->rootAliases = $rootAliases;
|
||||
$this->rootReferences = $rootReferences;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->poolOptimizer = $poolOptimizer;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
|
@ -259,6 +264,8 @@ class PoolBuilder
|
|||
$this->skippedLoad = array();
|
||||
$this->indexCounter = 0;
|
||||
|
||||
$pool = $this->runOptimizer($request, $pool);
|
||||
|
||||
Intervals::clear();
|
||||
|
||||
return $pool;
|
||||
|
@ -572,4 +579,33 @@ class PoolBuilder
|
|||
unset($this->aliasMap[spl_object_hash($package)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pool
|
||||
*/
|
||||
private function runOptimizer(Request $request, Pool $pool)
|
||||
{
|
||||
if (null === $this->poolOptimizer) {
|
||||
return $pool;
|
||||
}
|
||||
|
||||
$total = \count($pool->getPackages());
|
||||
|
||||
$pool = $this->poolOptimizer->optimize($request, $pool);
|
||||
|
||||
$filtered = $total - \count($pool->getPackages());
|
||||
|
||||
if (0 === $filtered) {
|
||||
return $pool;
|
||||
}
|
||||
|
||||
$this->io->write(sprintf(
|
||||
'<info>Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.</info>',
|
||||
number_format($total),
|
||||
number_format($filtered),
|
||||
round(100/$total*$filtered)
|
||||
), true, IOInterface::VERY_VERBOSE);
|
||||
|
||||
return $pool;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,386 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\DependencyResolver;
|
||||
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Semver\CompilingMatcher;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Semver\Intervals;
|
||||
|
||||
/**
|
||||
* Optimizes a given pool
|
||||
*
|
||||
* @author Yanick Witschi <yanick.witschi@terminal42.ch>
|
||||
*/
|
||||
class PoolOptimizer
|
||||
{
|
||||
/**
|
||||
* @var PolicyInterface
|
||||
*/
|
||||
private $policy;
|
||||
|
||||
/**
|
||||
* @var array<int, true>
|
||||
*/
|
||||
private $irremovablePackages = array();
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, ConstraintInterface>>
|
||||
*/
|
||||
private $requireConstraintsPerPackage = array();
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, ConstraintInterface>>
|
||||
*/
|
||||
private $conflictConstraintsPerPackage = array();
|
||||
|
||||
/**
|
||||
* @var array<int, true>
|
||||
*/
|
||||
private $packagesToRemove = array();
|
||||
|
||||
/**
|
||||
* @var array<int, BasePackage[]>
|
||||
*/
|
||||
private $aliasesPerPackage = array();
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
*/
|
||||
private $removedVersionsByPackage = array();
|
||||
|
||||
public function __construct(PolicyInterface $policy)
|
||||
{
|
||||
$this->policy = $policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pool
|
||||
*/
|
||||
public function optimize(Request $request, Pool $pool)
|
||||
{
|
||||
$this->prepare($request, $pool);
|
||||
|
||||
$optimizedPool = $this->optimizeByIdenticalDependencies($pool);
|
||||
|
||||
// No need to run this recursively at the moment
|
||||
// because the current optimizations cannot provide
|
||||
// even more gains when ran again. Might change
|
||||
// in the future with additional optimizations.
|
||||
|
||||
$this->irremovablePackages = array();
|
||||
$this->requireConstraintsPerPackage = array();
|
||||
$this->conflictConstraintsPerPackage = array();
|
||||
$this->packagesToRemove = array();
|
||||
$this->aliasesPerPackage = array();
|
||||
$this->removedVersionsByPackage = array();
|
||||
|
||||
return $optimizedPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function prepare(Request $request, Pool $pool)
|
||||
{
|
||||
$irremovablePackageConstraintGroups = array();
|
||||
|
||||
// Mark fixed or locked packages as irremovable
|
||||
foreach ($request->getFixedOrLockedPackages() as $package) {
|
||||
$irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion());
|
||||
}
|
||||
|
||||
// Extract requested package requirements
|
||||
foreach ($request->getRequires() as $require => $constraint) {
|
||||
$constraint = Intervals::compactConstraint($constraint);
|
||||
$this->requireConstraintsPerPackage[$require][(string) $constraint] = $constraint;
|
||||
}
|
||||
|
||||
// First pass over all packages to extract information and mark package constraints irremovable
|
||||
foreach ($pool->getPackages() as $package) {
|
||||
// Extract package requirements
|
||||
foreach ($package->getRequires() as $link) {
|
||||
$constraint = Intervals::compactConstraint($link->getConstraint());
|
||||
$this->requireConstraintsPerPackage[$link->getTarget()][(string) $constraint] = $constraint;
|
||||
}
|
||||
// Extract package conflicts
|
||||
foreach ($package->getConflicts() as $link) {
|
||||
$constraint = Intervals::compactConstraint($link->getConstraint());
|
||||
$this->conflictConstraintsPerPackage[$link->getTarget()][(string) $constraint] = $constraint;
|
||||
}
|
||||
|
||||
// Keep track of alias packages for every package so if either the alias or aliased is kept
|
||||
// we keep the others as they are a unit of packages really
|
||||
if ($package instanceof AliasPackage) {
|
||||
$this->aliasesPerPackage[$package->getAliasOf()->id][] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
$irremovablePackageConstraints = array();
|
||||
foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) {
|
||||
$irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false);
|
||||
}
|
||||
unset($irremovablePackageConstraintGroups);
|
||||
|
||||
// Mark the packages as irremovable based on the constraints
|
||||
foreach ($pool->getPackages() as $package) {
|
||||
if (!isset($irremovablePackageConstraints[$package->getName()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) {
|
||||
$this->markPackageIrremovable($package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function markPackageIrremovable(BasePackage $package)
|
||||
{
|
||||
$this->irremovablePackages[$package->id] = true;
|
||||
if ($package instanceof AliasPackage) {
|
||||
// recursing here so aliasesPerPackage for the aliasOf can be checked
|
||||
// and all its aliases marked as irremovable as well
|
||||
$this->markPackageIrremovable($package->getAliasOf());
|
||||
}
|
||||
if (isset($this->aliasesPerPackage[$package->id])) {
|
||||
foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
|
||||
$this->irremovablePackages[$aliasPackage->id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pool Optimized pool
|
||||
*/
|
||||
private function applyRemovalsToPool(Pool $pool)
|
||||
{
|
||||
$packages = array();
|
||||
$removedVersions = array();
|
||||
foreach ($pool->getPackages() as $package) {
|
||||
if (!isset($this->packagesToRemove[$package->id])) {
|
||||
$packages[] = $package;
|
||||
} else {
|
||||
$removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion();
|
||||
}
|
||||
}
|
||||
|
||||
$optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage);
|
||||
|
||||
// Reset package removals
|
||||
$this->packagesToRemove = array();
|
||||
|
||||
return $optimizedPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pool
|
||||
*/
|
||||
private function optimizeByIdenticalDependencies(Pool $pool)
|
||||
{
|
||||
$identicalDefinitionsPerPackage = array();
|
||||
$packageIdenticalDefinitionLookup = array();
|
||||
|
||||
foreach ($pool->getPackages() as $package) {
|
||||
|
||||
// If that package was already marked irremovable, we can skip
|
||||
// the entire process for it
|
||||
if (isset($this->irremovablePackages[$package->id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->markPackageForRemoval($package->id);
|
||||
|
||||
$dependencyHash = $this->calculateDependencyHash($package);
|
||||
|
||||
foreach ($package->getNames(false) as $packageName) {
|
||||
|
||||
if (!isset($this->requireConstraintsPerPackage[$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) {
|
||||
$groupHashParts = array();
|
||||
|
||||
if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) {
|
||||
$groupHashParts[] = 'require:' . (string) $requireConstraint;
|
||||
}
|
||||
|
||||
if ($package->getReplaces()) {
|
||||
foreach ($package->getReplaces() as $link) {
|
||||
if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) {
|
||||
// Use the same hash part as the regular require hash because that's what the replacement does
|
||||
$groupHashParts[] = 'require:' . (string) $link->getConstraint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->conflictConstraintsPerPackage[$packageName])) {
|
||||
foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) {
|
||||
if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) {
|
||||
$groupHashParts[] = 'conflict:' . (string) $conflictConstraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$groupHashParts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupHash = implode('', $groupHashParts);
|
||||
$identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package;
|
||||
$packageIdenticalDefinitionLookup[$package->id][$packageName] = array('groupHash' => $groupHash, 'dependencyHash' => $dependencyHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($identicalDefinitionsPerPackage as $constraintGroups) {
|
||||
foreach ($constraintGroups as $constraintGroup) {
|
||||
foreach ($constraintGroup as $packages) {
|
||||
// Only one package in this constraint group has the same requirements, we're not allowed to remove that package
|
||||
if (1 === \count($packages)) {
|
||||
$this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise we find out which one is the preferred package in this constraint group which is
|
||||
// then not allowed to be removed either
|
||||
$literals = array();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$literals[] = $package->id;
|
||||
}
|
||||
|
||||
foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) {
|
||||
$this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->applyRemovalsToPool($pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function calculateDependencyHash(BasePackage $package)
|
||||
{
|
||||
$hash = '';
|
||||
|
||||
$hashRelevantLinks = array(
|
||||
'requires' => $package->getRequires(),
|
||||
'conflicts' => $package->getConflicts(),
|
||||
'replaces' => $package->getReplaces(),
|
||||
'provides' => $package->getProvides()
|
||||
);
|
||||
|
||||
foreach ($hashRelevantLinks as $key => $links) {
|
||||
if (0 === \count($links)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// start new hash section
|
||||
$hash .= $key . ':';
|
||||
|
||||
$subhash = array();
|
||||
|
||||
foreach ($links as $link) {
|
||||
// To get the best dependency hash matches we should use Intervals::compactConstraint() here.
|
||||
// However, the majority of projects are going to specify their constraints already pretty
|
||||
// much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt
|
||||
// performance more than the additional few packages that could be filtered out would benefit the process.
|
||||
$subhash[$link->getTarget()] = (string) $link->getConstraint();
|
||||
}
|
||||
|
||||
// Sort for best result
|
||||
ksort($subhash);
|
||||
|
||||
foreach ($subhash as $target => $constraint) {
|
||||
$hash .= $target . '@' . $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return void
|
||||
*/
|
||||
private function markPackageForRemoval($id)
|
||||
{
|
||||
// We are not allowed to remove packages if they have been marked as irremovable
|
||||
if (isset($this->irremovablePackages[$id])) {
|
||||
throw new \LogicException('Attempted removing a package which was previously marked irremovable');
|
||||
}
|
||||
|
||||
$this->packagesToRemove[$id] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array<string, list<BasePackage>>>> $identicalDefinitionsPerPackage
|
||||
* @param array<int, array<string, array{groupHash: string, dependencyHash: string}>> $packageIdenticalDefinitionLookup
|
||||
* @return void
|
||||
*/
|
||||
private function keepPackage(BasePackage $package, $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup)
|
||||
{
|
||||
unset($this->packagesToRemove[$package->id]);
|
||||
|
||||
if ($package instanceof AliasPackage) {
|
||||
// recursing here so aliasesPerPackage for the aliasOf can be checked
|
||||
// and all its aliases marked to be kept as well
|
||||
$this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
|
||||
}
|
||||
|
||||
// record all the versions of the package group so we can list them later in Problem output
|
||||
foreach ($package->getNames(false) as $name) {
|
||||
if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) {
|
||||
$packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name];
|
||||
$packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
|
||||
foreach ($packageGroup as $pkg) {
|
||||
if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
|
||||
$pkg = $pkg->getAliasOf();
|
||||
}
|
||||
$this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->aliasesPerPackage[$package->id])) {
|
||||
foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
|
||||
unset($this->packagesToRemove[$aliasPackage->id]);
|
||||
|
||||
// record all the versions of the package group so we can list them later in Problem output
|
||||
foreach ($aliasPackage->getNames(false) as $name) {
|
||||
if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) {
|
||||
$packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name];
|
||||
$packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
|
||||
foreach ($packageGroup as $pkg) {
|
||||
if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
|
||||
$pkg = $pkg->getAliasOf();
|
||||
}
|
||||
$this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -126,6 +126,10 @@ class Problem
|
|||
$template = preg_replace('{^\S+ \S+ }', '%s%s ', $message);
|
||||
$messages[] = $template;
|
||||
$templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2];
|
||||
$sourcePackage = $rule->getSourcePackage($pool);
|
||||
foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) {
|
||||
$templates[$template][$m[1]][$version] = $prettyVersion;
|
||||
}
|
||||
} elseif ($message !== '') {
|
||||
$messages[] = $message;
|
||||
}
|
||||
|
@ -267,7 +271,7 @@ class Problem
|
|||
return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion()));
|
||||
});
|
||||
if (0 === count($filtered)) {
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,7 +281,7 @@ class Problem
|
|||
return $fixedConstraint->matches(new Constraint('==', $p->getVersion()));
|
||||
});
|
||||
if (0 === count($filtered)) {
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,27 +290,27 @@ class Problem
|
|||
});
|
||||
|
||||
if (!$nonLockedPackages) {
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.');
|
||||
}
|
||||
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.');
|
||||
}
|
||||
|
||||
// check if the package is found when bypassing stability checks
|
||||
if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
|
||||
// we must first verify if a valid package would be found in a lower priority repository
|
||||
if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
|
||||
return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint);
|
||||
return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint);
|
||||
}
|
||||
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.');
|
||||
}
|
||||
|
||||
// check if the package is found when bypassing the constraint and stability checks
|
||||
if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
|
||||
// we must first verify if a valid package would be found in a lower priority repository
|
||||
if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
|
||||
return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint);
|
||||
return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint);
|
||||
}
|
||||
|
||||
$suffix = '';
|
||||
|
@ -326,7 +330,7 @@ class Problem
|
|||
$suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.';
|
||||
}
|
||||
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix);
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix);
|
||||
}
|
||||
|
||||
if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
|
||||
|
@ -356,15 +360,26 @@ class Problem
|
|||
* @internal
|
||||
* @param PackageInterface[] $packages
|
||||
* @param bool $isVerbose
|
||||
* @param bool $useRemovedVersionGroup
|
||||
* @return string
|
||||
*/
|
||||
public static function getPackageList(array $packages, $isVerbose)
|
||||
public static function getPackageList(array $packages, $isVerbose, Pool $pool = null, ConstraintInterface $constraint = null, $useRemovedVersionGroup = false)
|
||||
{
|
||||
$prepared = array();
|
||||
$hasDefaultBranch = array();
|
||||
foreach ($packages as $package) {
|
||||
$prepared[$package->getName()]['name'] = $package->getPrettyName();
|
||||
$prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : '');
|
||||
if ($pool && $constraint) {
|
||||
foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) {
|
||||
$prepared[$package->getName()]['versions'][$version] = $prettyVersion;
|
||||
}
|
||||
}
|
||||
if ($pool && $useRemovedVersionGroup) {
|
||||
foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) {
|
||||
$prepared[$package->getName()]['versions'][$version] = $prettyVersion;
|
||||
}
|
||||
}
|
||||
if ($package->isDefaultBranch()) {
|
||||
$hasDefaultBranch[$package->getName()] = true;
|
||||
}
|
||||
|
@ -469,7 +484,7 @@ class Problem
|
|||
* @param string $reason
|
||||
* @return array{0: string, 1: string}
|
||||
*/
|
||||
private static function computeCheckForLowerPrioRepo($isVerbose, $packageName, array $higherRepoPackages, array $allReposPackages, $reason, ConstraintInterface $constraint = null)
|
||||
private static function computeCheckForLowerPrioRepo(Pool $pool, $isVerbose, $packageName, array $higherRepoPackages, array $allReposPackages, $reason, ConstraintInterface $constraint = null)
|
||||
{
|
||||
$nextRepoPackages = array();
|
||||
$nextRepo = null;
|
||||
|
@ -488,7 +503,7 @@ class Problem
|
|||
if ($topPackage instanceof RootPackageInterface) {
|
||||
return array(
|
||||
"- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ',
|
||||
'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.',
|
||||
'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -497,10 +512,10 @@ class Problem
|
|||
$singular = count($higherRepoPackages) === 1;
|
||||
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ',
|
||||
'found '.self::getPackageList($nextRepoPackages, $isVerbose).' in the lock file and '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.', );
|
||||
'found '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' in the lock file and '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.', );
|
||||
}
|
||||
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.');
|
||||
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -228,6 +228,41 @@ abstract class Rule
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return BasePackage
|
||||
*/
|
||||
public function getSourcePackage(Pool $pool)
|
||||
{
|
||||
$literals = $this->getLiterals();
|
||||
|
||||
switch ($this->getReason()) {
|
||||
case self::RULE_PACKAGE_CONFLICT:
|
||||
$package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
|
||||
$package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));
|
||||
|
||||
$conflictTarget = $package1->getPrettyString();
|
||||
if ($reasonData = $this->getReasonData()) {
|
||||
// swap literals if they are not in the right order with package2 being the conflicter
|
||||
if ($reasonData->getSource() === $package1->getName()) {
|
||||
list($package2, $package1) = array($package1, $package2);
|
||||
}
|
||||
}
|
||||
|
||||
return $package2;
|
||||
|
||||
case self::RULE_PACKAGE_REQUIRES:
|
||||
$sourceLiteral = array_shift($literals);
|
||||
$sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));
|
||||
|
||||
return $sourcePackage;
|
||||
|
||||
default:
|
||||
throw new \LogicException('Not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $isVerbose
|
||||
* @param BasePackage[] $installedMap
|
||||
|
@ -258,7 +293,7 @@ abstract class Rule
|
|||
}
|
||||
}
|
||||
|
||||
return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose).'.';
|
||||
return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.';
|
||||
|
||||
case self::RULE_FIXED:
|
||||
$package = $this->deduplicateDefaultBranchAlias($this->reasonData['package']);
|
||||
|
@ -320,7 +355,7 @@ abstract class Rule
|
|||
|
||||
$text = $reasonData->getPrettyString($sourcePackage);
|
||||
if ($requires) {
|
||||
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose) . '.';
|
||||
$text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $this->reasonData->getConstraint()) . '.';
|
||||
} else {
|
||||
$targetName = $reasonData->getTarget();
|
||||
|
||||
|
@ -368,13 +403,13 @@ abstract class Rule
|
|||
}
|
||||
|
||||
if ($installedPackages && $removablePackages) {
|
||||
return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose).'. '.$reason;
|
||||
return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason;
|
||||
}
|
||||
|
||||
return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose).'. '.$reason;
|
||||
return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason;
|
||||
}
|
||||
|
||||
return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose) . '.';
|
||||
return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.';
|
||||
case self::RULE_LEARNED:
|
||||
/** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */
|
||||
// if (isset($learnedPool[$this->reasonData])) {
|
||||
|
@ -445,9 +480,10 @@ abstract class Rule
|
|||
/**
|
||||
* @param array<int|BasePackage> $packages An array containing packages or literals
|
||||
* @param bool $isVerbose
|
||||
* @param bool $useRemovedVersionGroup
|
||||
* @return string
|
||||
*/
|
||||
protected function formatPackagesUnique(Pool $pool, array $packages, $isVerbose)
|
||||
protected function formatPackagesUnique(Pool $pool, array $packages, $isVerbose, ConstraintInterface $constraint = null, $useRemovedVersionGroup = false)
|
||||
{
|
||||
foreach ($packages as $index => $package) {
|
||||
if (!\is_object($package)) {
|
||||
|
@ -455,7 +491,7 @@ abstract class Rule
|
|||
}
|
||||
}
|
||||
|
||||
return Problem::getPackageList($packages, $isVerbose);
|
||||
return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\DependencyResolver\LockTransaction;
|
|||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\DependencyResolver\Solver;
|
||||
|
@ -430,35 +431,98 @@ class Installer
|
|||
$request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies);
|
||||
}
|
||||
|
||||
$pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher);
|
||||
if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') || '2' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) {
|
||||
$pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy));
|
||||
} else {
|
||||
$pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher/*, $this->createPoolOptimizer($policy)*/);
|
||||
}
|
||||
|
||||
$this->io->writeError('<info>Updating dependencies</info>');
|
||||
|
||||
// solve dependencies
|
||||
$solver = new Solver($policy, $pool, $this->io);
|
||||
try {
|
||||
$lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
|
||||
$ruleSetSize = $solver->getRuleSetSize();
|
||||
$solver = null;
|
||||
} catch (SolverProblemsException $e) {
|
||||
$err = 'Your requirements could not be resolved to an installable set of packages.';
|
||||
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());
|
||||
if (!Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') && '1' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) {
|
||||
try {
|
||||
$this->io->writeError("<highlight>Updating dependencies with default package pool</highlight>", true, IOInterface::VERBOSE);
|
||||
$lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
|
||||
$ruleSetSize = $solver->getRuleSetSize();
|
||||
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
$this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
|
||||
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
|
||||
$this->io->writeError($prettyProblem);
|
||||
if (!$this->devMode) {
|
||||
$this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
|
||||
$this->io->writeError("<highlight>Updating dependencies with optimized package pool</highlight>", true, IOInterface::VERBOSE);
|
||||
$pool2 = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy));
|
||||
$solver2 = new Solver($policy, $pool2, $this->io);
|
||||
$lockTransaction2 = $solver2->solve($request, $this->platformRequirementFilter);
|
||||
$ruleSetSize2 = $solver2->getRuleSetSize();
|
||||
$this->io->writeError("Analyzed ".count($pool2)." packages to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
$this->io->writeError("Analyzed ".$ruleSetSize2." rules to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
|
||||
$solver = $solver2 = null;
|
||||
$pool = $pool2 = null;
|
||||
} catch (SolverProblemsException $e) {
|
||||
$err = 'Your requirements could not be resolved to an installable set of packages.';
|
||||
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());
|
||||
|
||||
if (isset($pool2)) {
|
||||
throw new \LogicException('Optimized solver failed but non-optimized one did not fail, please report this with your composer.json');
|
||||
} else {
|
||||
try {
|
||||
$pool2 = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy));
|
||||
$solver2 = new Solver($policy, $pool2, $this->io);
|
||||
$lockTransaction2 = $solver2->solve($request, $this->platformRequirementFilter);
|
||||
throw new \LogicException('Optimized solver worked but non-optimized one failed resolving, please report this with your composer.json');
|
||||
} catch (SolverProblemsException $e2) {
|
||||
}
|
||||
}
|
||||
|
||||
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
|
||||
$this->io->writeError($prettyProblem);
|
||||
if (!$this->devMode) {
|
||||
$this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
|
||||
}
|
||||
|
||||
$ghe = new GithubActionError($this->io);
|
||||
$ghe->emit($err."\n".$prettyProblem);
|
||||
|
||||
return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
|
||||
}
|
||||
|
||||
$ghe = new GithubActionError($this->io);
|
||||
$ghe->emit($err."\n".$prettyProblem);
|
||||
$txLogOptimized = array_map(function ($op) {
|
||||
return (string) $op;
|
||||
}, $lockTransaction2->getOperations());
|
||||
$txLogRaw = array_map(function ($op) {
|
||||
return (string) $op;
|
||||
}, $lockTransaction->getOperations());
|
||||
if ($txLogOptimized !== $txLogRaw) {
|
||||
throw new \LogicException('Optimized solver resolved differently from non-optimized one, please report this with your composer.json'.PHP_EOL.implode(PHP_EOL,$txLogOptimized).implode(PHP_EOL,$txLogRaw));
|
||||
}
|
||||
$this->io->writeError("<highlight>Done, test successful</highlight>", true, IOInterface::VERBOSE);
|
||||
} else {
|
||||
try {
|
||||
$lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
|
||||
$ruleSetSize = $solver->getRuleSetSize();
|
||||
$solver = null;
|
||||
} catch (SolverProblemsException $e) {
|
||||
$err = 'Your requirements could not be resolved to an installable set of packages.';
|
||||
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());
|
||||
|
||||
return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
|
||||
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
|
||||
$this->io->writeError($prettyProblem);
|
||||
if (!$this->devMode) {
|
||||
$this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
|
||||
}
|
||||
|
||||
$ghe = new GithubActionError($this->io);
|
||||
$ghe->emit($err."\n".$prettyProblem);
|
||||
|
||||
return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
|
||||
}
|
||||
|
||||
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
$this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
$pool = null;
|
||||
}
|
||||
|
||||
$this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
$this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);
|
||||
|
||||
if (!$lockTransaction->getOperations()) {
|
||||
$this->io->writeError('Nothing to modify in lock file');
|
||||
}
|
||||
|
@ -999,6 +1063,23 @@ class Installer
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PoolOptimizer|null
|
||||
*/
|
||||
private function createPoolOptimizer(PolicyInterface $policy)
|
||||
{
|
||||
// Not the best architectural decision here, would need to be able
|
||||
// to configure from the outside of Installer but this is only
|
||||
// a debugging tool and should never be required in any other use case
|
||||
if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) {
|
||||
$this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PoolOptimizer($policy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Installer
|
||||
*
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Repository;
|
||||
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\DependencyResolver\PolicyInterface;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolBuilder;
|
||||
use Composer\DependencyResolver\Request;
|
||||
|
@ -244,9 +246,9 @@ class RepositorySet
|
|||
*
|
||||
* @return Pool
|
||||
*/
|
||||
public function createPool(Request $request, IOInterface $io, EventDispatcher $eventDispatcher = null)
|
||||
public function createPool(Request $request, IOInterface $io, EventDispatcher $eventDispatcher = null, PoolOptimizer $poolOptimizer = null)
|
||||
{
|
||||
$poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher);
|
||||
$poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer);
|
||||
|
||||
foreach ($this->repositories as $repo) {
|
||||
if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
|
||||
|
|
|
@ -105,3 +105,13 @@ Check that replacers from additional repositories are loaded when doing a partia
|
|||
"shared/dep-1.0.0.0",
|
||||
"shared/dep-1.2.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"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.2.0.0"
|
||||
]
|
||||
|
|
|
@ -96,3 +96,13 @@ Check that replacers from additional repositories are loaded
|
|||
"replacer/package-1.0.0.0",
|
||||
"shared/dep-1.0.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"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"
|
||||
]
|
||||
|
|
|
@ -48,3 +48,13 @@ locked packages still need to be taking into account for loading all necessary v
|
|||
"dep/pkg1-1.0.1.0",
|
||||
"dep/pkg1-2.0.0.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"root/req1-1.0.0.0 (locked)",
|
||||
"root/req2-1.0.0.0 (locked)",
|
||||
"dep/pkg2-1.0.0.0",
|
||||
"dep/pkg2-1.2.0.0",
|
||||
"dep/pkg1-1.0.1.0",
|
||||
"dep/pkg1-2.0.0.0"
|
||||
]
|
||||
|
|
|
@ -50,3 +50,12 @@ Fixed packages and replacers get unfixed correctly (refs https://github.com/comp
|
|||
"replaced/pkg-1.2.3.0",
|
||||
"replaced/pkg-1.2.4.0"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
"root/req3-1.0.0.0 (locked)",
|
||||
"dep/dep-2.3.5.0 (locked)",
|
||||
"root/req1-1.1.0.0",
|
||||
"replacer/pkg-1.1.0.0",
|
||||
"replaced/pkg-1.2.4.0"
|
||||
]
|
||||
|
|
|
@ -46,3 +46,10 @@ Stability flags apply
|
|||
6,
|
||||
"default/pkg-1.2.0.0 (alias of 6)"
|
||||
]
|
||||
|
||||
--EXPECT-OPTIMIZED--
|
||||
[
|
||||
1,
|
||||
6,
|
||||
"default/pkg-1.2.0.0 (alias of 6)"
|
||||
]
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
--TEST--
|
||||
Test aliased and aliasees remain untouched if either is required, but are still optimized away otherwise.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0",
|
||||
"package/required-aliasof-and-alias": "dev-main-both",
|
||||
"package/required-aliasof": "dev-main-direct",
|
||||
"package/required-alias": "1.*"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/required-aliasof-and-alias": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof-and-alias",
|
||||
"version": "dev-main-both",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-both": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof",
|
||||
"version": "dev-main-direct",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-direct": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-alias",
|
||||
"version": "dev-main-alias",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-alias": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/not-referenced",
|
||||
"version": "dev-lonesome-pkg",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-lonesome-pkg": "1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof-and-alias",
|
||||
"version": "dev-main-both",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-both": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-aliasof",
|
||||
"version": "dev-main-direct",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-direct": "1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/required-alias",
|
||||
"version": "dev-main-alias",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main-alias": "1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,46 @@
|
|||
--TEST--
|
||||
Test filters irrelevant package "package/b" in version 1.0.0
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
--TEST--
|
||||
Test filters irrelevant package "package/b" in version 1.0.1 because prefer-lowest
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
},
|
||||
"preferLowest": true
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
--TEST--
|
||||
We have to make sure, conflicts are considered in the grouping so we do not remove packages
|
||||
from the pool which might end up being part of the solution.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"nesty/nest": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.1 || 1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.2.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,103 @@
|
|||
--TEST--
|
||||
We have to make sure, conflicts are considered in the grouping so we do not remove packages
|
||||
from the pool which might end up being part of the solution.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"nesty/nest": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.0.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.2.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "nesty/nest",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.1",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.0 || 1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "conflicter/pkg",
|
||||
"version": "1.0.2",
|
||||
"conflict": {
|
||||
"victim/pkg": "1.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"name": "victim/pkg",
|
||||
"version": "1.1.2"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,99 @@
|
|||
--TEST--
|
||||
We are not allowed to group packages only by their dependency definition. It's also relevant what other
|
||||
packages require (package/b@1.0.1 must not be dropped although it has the very same definition as 2.0.0 and both are
|
||||
allowed by the request). However, package/b@1.0.0 can be removed.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0 || ^2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "2.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/d": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/d",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": ">=1.0 <1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0 || ^2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "2.0.0",
|
||||
"require": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/d": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/d",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": ">=1.0 <1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
--TEST--
|
||||
Test locked and fixed packages remain untouched.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
},
|
||||
"locked": [
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"fixed": [
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,59 @@
|
|||
--TEST--
|
||||
Test replaced packages are correctly removed.
|
||||
|
||||
--REQUEST--
|
||||
{
|
||||
"require": {
|
||||
"package/a": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--POOL-BEFORE--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.0",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/c",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
--POOL-AFTER--
|
||||
[
|
||||
{
|
||||
"name": "package/a",
|
||||
"version": "1.0.0",
|
||||
"require": {
|
||||
"package/b": "^1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "package/b",
|
||||
"version": "1.0.1",
|
||||
"replace": {
|
||||
"package/c": "^1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
namespace Composer\Test\DependencyResolver;
|
||||
|
||||
use Composer\DependencyResolver\DefaultPolicy;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Repository\FilterRepository;
|
||||
|
@ -31,13 +34,14 @@ class PoolBuilderTest extends TestCase
|
|||
* @dataProvider getIntegrationTests
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param mixed[] $expect
|
||||
* @param string[] $expect
|
||||
* @param string[] $expectOptimized
|
||||
* @param mixed[] $root
|
||||
* @param mixed[] $requestData
|
||||
* @param mixed[] $packageRepos
|
||||
* @param mixed[] $fixed
|
||||
*/
|
||||
public function testPoolBuilder($file, $message, $expect, $root, $requestData, $packageRepos, $fixed)
|
||||
public function testPoolBuilder($file, $message, $expect, $expectOptimized, $root, $requestData, $packageRepos, $fixed)
|
||||
{
|
||||
$rootAliases = !empty($root['aliases']) ? $root['aliases'] : array();
|
||||
$minimumStability = !empty($root['minimum-stability']) ? $root['minimum-stability'] : 'stable';
|
||||
|
@ -56,6 +60,8 @@ class PoolBuilderTest extends TestCase
|
|||
$loader = new ArrayLoader();
|
||||
$packageIds = array();
|
||||
$loadPackage = function ($data) use ($loader, &$packageIds) {
|
||||
/** @var ?int $id */
|
||||
$id = null;
|
||||
if (!empty($data['id'])) {
|
||||
$id = $data['id'];
|
||||
unset($data['id']);
|
||||
|
@ -115,12 +121,28 @@ class PoolBuilderTest extends TestCase
|
|||
}
|
||||
|
||||
$pool = $repositorySet->createPool($request, new NullIO());
|
||||
|
||||
$result = $this->getPackageResultSet($pool, $packageIds);
|
||||
|
||||
$this->assertSame($expect, $result, 'Unoptimized pool does not match expected package set');
|
||||
|
||||
$optimizer = new PoolOptimizer(new DefaultPolicy());
|
||||
$result = $this->getPackageResultSet($optimizer->optimize($request, $pool), $packageIds);
|
||||
$this->assertSame($expectOptimized, $result, 'Optimized pool does not match expected package set');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, BasePackage> $packageIds
|
||||
* @return string[]
|
||||
*/
|
||||
private function getPackageResultSet(Pool $pool, $packageIds)
|
||||
{
|
||||
$result = array();
|
||||
for ($i = 1, $count = count($pool); $i <= $count; $i++) {
|
||||
$result[] = $pool->packageById($i);
|
||||
}
|
||||
|
||||
$result = array_map(function ($package) use ($packageIds) {
|
||||
return array_map(function (BasePackage $package) use ($packageIds) {
|
||||
if ($id = array_search($package, $packageIds, true)) {
|
||||
return $id;
|
||||
}
|
||||
|
@ -143,8 +165,6 @@ class PoolBuilderTest extends TestCase
|
|||
|
||||
return (string) $package->getName().'-'.$package->getVersion() . $suffix;
|
||||
}, $result);
|
||||
|
||||
$this->assertSame($expect, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,11 +193,12 @@ class PoolBuilderTest extends TestCase
|
|||
$fixed = JsonFile::parseJson($testData['FIXED']);
|
||||
}
|
||||
$expect = JsonFile::parseJson($testData['EXPECT']);
|
||||
$expectOptimized = !empty($testData['EXPECT-OPTIMIZED']) ? JsonFile::parseJson($testData['EXPECT-OPTIMIZED']) : $expect;
|
||||
} catch (\Exception $e) {
|
||||
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, $packageRepos, $fixed);
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $expect, $expectOptimized, $root, $request, $packageRepos, $fixed);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
|
@ -199,6 +220,7 @@ class PoolBuilderTest extends TestCase
|
|||
'FIXED' => false,
|
||||
'PACKAGE-REPOS' => true,
|
||||
'EXPECT' => true,
|
||||
'EXPECT-OPTIMIZED' => false,
|
||||
);
|
||||
|
||||
$section = null;
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test\DependencyResolver;
|
||||
|
||||
use Composer\DependencyResolver\DefaultPolicy;
|
||||
use Composer\DependencyResolver\Pool;
|
||||
use Composer\DependencyResolver\PoolOptimizer;
|
||||
use Composer\DependencyResolver\Request;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Loader\ArrayLoader;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Test\TestCase;
|
||||
|
||||
class PoolOptimizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideIntegrationTests
|
||||
* @param mixed[] $requestData
|
||||
* @param BasePackage[] $packagesBefore
|
||||
* @param BasePackage[] $expectedPackages
|
||||
* @param string $message
|
||||
*/
|
||||
public function testPoolOptimizer(array $requestData, array $packagesBefore, array $expectedPackages, $message)
|
||||
{
|
||||
$lockedRepo = new LockArrayRepository();
|
||||
|
||||
$request = new Request($lockedRepo);
|
||||
$parser = new VersionParser();
|
||||
|
||||
if (isset($requestData['locked'])) {
|
||||
foreach ($requestData['locked'] as $package) {
|
||||
$request->lockPackage($this->loadPackage($package));
|
||||
}
|
||||
}
|
||||
if (isset($requestData['fixed'])) {
|
||||
foreach ($requestData['fixed'] as $package) {
|
||||
$request->fixPackage($this->loadPackage($package));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($requestData['require'] as $package => $constraint) {
|
||||
$request->requireName($package, $parser->parseConstraints($constraint));
|
||||
}
|
||||
|
||||
$preferStable = isset($requestData['preferStable']) ? $requestData['preferStable'] : false;
|
||||
$preferLowest = isset($requestData['preferLowest']) ? $requestData['preferLowest'] : false;
|
||||
|
||||
$pool = new Pool($packagesBefore);
|
||||
$poolOptimizer = new PoolOptimizer(new DefaultPolicy($preferStable, $preferLowest));
|
||||
|
||||
$pool = $poolOptimizer->optimize($request, $pool);
|
||||
|
||||
$this->assertSame(
|
||||
$this->reducePackagesInfoForComparison($expectedPackages),
|
||||
$this->reducePackagesInfoForComparison($pool->getPackages()),
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
public function provideIntegrationTests()
|
||||
{
|
||||
$fixturesDir = realpath(__DIR__.'/Fixtures/pooloptimizer/');
|
||||
$tests = array();
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
if (!preg_match('/\.test$/', $file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$testData = $this->readTestFile($file, $fixturesDir);
|
||||
$message = $testData['TEST'];
|
||||
$requestData = JsonFile::parseJson($testData['REQUEST']);
|
||||
$packagesBefore = $this->loadPackages(JsonFile::parseJson($testData['POOL-BEFORE']));
|
||||
$expectedPackages = $this->loadPackages(JsonFile::parseJson($testData['POOL-AFTER']));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[basename($file)] = array($requestData, $packagesBefore, $expectedPackages, $message);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fixturesDir
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function readTestFile(\SplFileInfo $file, $fixturesDir)
|
||||
{
|
||||
$tokens = preg_split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
/** @var array<string, bool> $sectionInfo */
|
||||
$sectionInfo = array(
|
||||
'TEST' => true,
|
||||
'REQUEST' => true,
|
||||
'POOL-BEFORE' => true,
|
||||
'POOL-AFTER' => true,
|
||||
);
|
||||
|
||||
$section = null;
|
||||
$data = array();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if (null === $section && empty($token)) {
|
||||
continue; // skip leading blank
|
||||
}
|
||||
|
||||
if (null === $section) {
|
||||
if (!isset($sectionInfo[$token])) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'The test file "%s" must not contain a section named "%s".',
|
||||
str_replace($fixturesDir.'/', '', $file),
|
||||
$token
|
||||
));
|
||||
}
|
||||
$section = $token;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sectionData = $token;
|
||||
|
||||
$data[$section] = $sectionData;
|
||||
$section = $sectionData = null;
|
||||
}
|
||||
|
||||
foreach ($sectionInfo as $section => $required) {
|
||||
if ($required && !isset($data[$section])) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'The test file "%s" must have a section named "%s".',
|
||||
str_replace($fixturesDir.'/', '', $file),
|
||||
$section
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BasePackage[] $packages
|
||||
* @return string[]
|
||||
*/
|
||||
private function reducePackagesInfoForComparison(array $packages)
|
||||
{
|
||||
$packagesInfo = array();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$packagesInfo[] = $package->getName() . '@' . $package->getVersion() . ($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getVersion().')' : '');
|
||||
}
|
||||
|
||||
sort($packagesInfo);
|
||||
|
||||
return $packagesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[][] $packagesData
|
||||
* @return BasePackage[]
|
||||
*/
|
||||
private function loadPackages(array $packagesData)
|
||||
{
|
||||
$packages = array();
|
||||
|
||||
foreach ($packagesData as $packageData) {
|
||||
$packages[] = $package = $this->loadPackage($packageData);
|
||||
if ($package instanceof AliasPackage) {
|
||||
$packages[] = $package->getAliasOf();
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $packageData
|
||||
* @return BasePackage
|
||||
*/
|
||||
private function loadPackage(array $packageData)
|
||||
{
|
||||
$loader = new ArrayLoader();
|
||||
return $loader->load($packageData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
--TEST--
|
||||
|
||||
Test that a package which has a conflict does not get installed and has to be downgraded
|
||||
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "nesty/nest", "version": "1.0.0", "require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
} },
|
||||
{ "name": "conflicter/pkg", "version": "1.0.1", "conflict": { "victim/pkg": "1.1.0"} },
|
||||
{ "name": "victim/pkg", "version": "1.0.0" },
|
||||
{ "name": "victim/pkg", "version": "1.0.1" },
|
||||
{ "name": "victim/pkg", "version": "1.0.2" },
|
||||
{ "name": "victim/pkg", "version": "1.1.0" },
|
||||
{ "name": "victim/pkg", "version": "1.2.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"nesty/nest": "*"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--RUN--
|
||||
update
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
0
|
||||
|
||||
--EXPECT--
|
||||
Installing victim/pkg (1.0.2)
|
||||
Installing conflicter/pkg (1.0.1)
|
||||
Installing nesty/nest (1.0.0)
|
|
@ -0,0 +1,35 @@
|
|||
--TEST--
|
||||
|
||||
Test that a package which has a conflict does not get installed and has to be downgraded
|
||||
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "conflicter/pkg", "version": "1.0.1", "conflict": { "victim/pkg": "1.1.0"} },
|
||||
{ "name": "victim/pkg", "version": "1.0.0" },
|
||||
{ "name": "victim/pkg", "version": "1.0.1" },
|
||||
{ "name": "victim/pkg", "version": "1.0.2" },
|
||||
{ "name": "victim/pkg", "version": "1.1.0" },
|
||||
{ "name": "victim/pkg", "version": "1.2.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"conflicter/pkg": "^1.0",
|
||||
"victim/pkg": "^1 <1.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
--RUN--
|
||||
update
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
0
|
||||
|
||||
--EXPECT--
|
||||
Installing conflicter/pkg (1.0.1)
|
||||
Installing victim/pkg (1.0.2)
|
|
@ -11,15 +11,15 @@ Test the error output of solver problems is deduplicated.
|
|||
{ "name": "package/a", "version": "2.0.2", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.0.3", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.1.0", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.2.0", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.0" } },
|
||||
{ "name": "package/a", "version": "2.2.0", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.3.1", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.3.2", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.3.3", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.3.4", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.3.5", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.4.0", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.5.0", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "package/a", "version": "2.6.0", "require": { "missing/dep": "^1.1" } },
|
||||
{ "name": "missing/dep", "version": "2.0.0" }
|
||||
]
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ Updating dependencies
|
|||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- package/a[2.0.0, ..., 2.6.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint.
|
||||
- package/a[2.2.0, ..., 2.6.0] require missing/dep ^1.1 -> found missing/dep[2.0.0] but it does not match the constraint.
|
||||
- package/a[2.0.0, ..., 2.1.0] require missing/dep ^1.0 -> found missing/dep[2.0.0] but it does not match the constraint.
|
||||
- Root composer.json requires package/a * -> satisfiable by package/a[2.0.0, ..., 2.6.0].
|
||||
|
||||
--EXPECT--
|
||||
|
|
|
@ -128,6 +128,21 @@ Your requirements could not be resolved to an installable set of packages.
|
|||
- You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29].
|
||||
- illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10].
|
||||
- Conclusion: don't install symfony/console v3.1.10 (conflict analysis result)
|
||||
|
||||
--EXPECT-OUTPUT-OPTIMIZED--
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Root composer.json requires illuminate/queue * -> satisfiable by illuminate/queue[v5.2.0].
|
||||
- illuminate/queue v5.2.0 requires illuminate/console 5.2.* -> satisfiable by illuminate/console[v5.2.25, v5.2.26].
|
||||
- illuminate/console v5.2.25 requires symfony/console 3.1.* -> satisfiable by symfony/console[v3.1.9, v3.1.10].
|
||||
- illuminate/console v5.2.26 requires symfony/console 2.8.* -> satisfiable by symfony/console[v2.8.7, v2.8.8].
|
||||
- You can only install one version of a package, so only one of these can be installed: symfony/console[v2.8.7, v2.8.8, v3.1.9, ..., v3.4.29].
|
||||
- friendsofphp/php-cs-fixer[v2.10.4, ..., v2.10.5] require symfony/console ^3.2 || ^4.0 -> satisfiable by symfony/console[v3.2.13, ..., v3.4.29].
|
||||
- Root composer.json requires friendsofphp/php-cs-fixer * -> satisfiable by friendsofphp/php-cs-fixer[v2.10.4, v2.10.5].
|
||||
|
||||
--EXPECT--
|
||||
|
||||
--EXPECT-EXIT-CODE--
|
||||
|
|
|
@ -44,5 +44,15 @@ Your requirements could not be resolved to an installable set of packages.
|
|||
- Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3].
|
||||
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
|
||||
|
||||
--EXPECT-OUTPUT-OPTIMIZED--
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Only one of these can be installed: regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3], replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3]. replacer/pkg replaces regular/pkg and thus cannot coexist with it.
|
||||
- Root composer.json requires regular/pkg 1.* -> satisfiable by regular/pkg[1.0.0, 1.0.1, 1.0.2, 1.0.3].
|
||||
- Root composer.json requires replacer/pkg 2.* -> satisfiable by replacer/pkg[2.0.0, 2.0.1, 2.0.2, 2.0.3].
|
||||
|
||||
--EXPECT--
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ use Composer\Package\Locker;
|
|||
use Composer\Test\Mock\FactoryMock;
|
||||
use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
|
||||
use Composer\Test\Mock\InstallationManagerMock;
|
||||
use Composer\Util\Platform;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
@ -56,6 +57,8 @@ class InstallerTest extends TestCase
|
|||
|
||||
public function tearDown()
|
||||
{
|
||||
Platform::clearEnv('COMPOSER_POOL_OPTIMIZER');
|
||||
|
||||
chdir($this->prevCwd);
|
||||
if (isset($this->tempComposerHome) && is_dir($this->tempComposerHome)) {
|
||||
$fs = new Filesystem;
|
||||
|
@ -228,12 +231,15 @@ class InstallerTest extends TestCase
|
|||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testSlowIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
public function testSlowIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
return $this->testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,10 +254,56 @@ class InstallerTest extends TestCase
|
|||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
public function testIntegrationWithPoolOptimizer($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '1');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutputOptimized ?: $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideIntegrationTests
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param ?string $condition
|
||||
* @param Config $composerConfig
|
||||
* @param ?mixed[] $lock
|
||||
* @param ?mixed[] $installed
|
||||
* @param string $run
|
||||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param ?string $expectOutputOptimized
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
*/
|
||||
public function testIntegrationWithRawPool($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult)
|
||||
{
|
||||
Platform::putEnv('COMPOSER_POOL_OPTIMIZER', '0');
|
||||
|
||||
$this->doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $message
|
||||
* @param ?string $condition
|
||||
* @param Config $composerConfig
|
||||
* @param ?mixed[] $lock
|
||||
* @param ?mixed[] $installed
|
||||
* @param string $run
|
||||
* @param mixed[]|false $expectLock
|
||||
* @param ?mixed[] $expectInstalled
|
||||
* @param ?string $expectOutput
|
||||
* @param string $expect
|
||||
* @param int|string $expectResult
|
||||
* @return void
|
||||
*/
|
||||
private function doTestIntegration($file, $message, $condition, $composerConfig, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult)
|
||||
{
|
||||
if ($condition) {
|
||||
eval('$res = '.$condition.';');
|
||||
|
@ -518,6 +570,7 @@ class InstallerTest extends TestCase
|
|||
$expectInstalled = JsonFile::parseJson($testData['EXPECT-INSTALLED']);
|
||||
}
|
||||
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
|
||||
$expectOutputOptimized = isset($testData['EXPECT-OUTPUT-OPTIMIZED']) ? $testData['EXPECT-OUTPUT-OPTIMIZED'] : null;
|
||||
$expect = $testData['EXPECT'];
|
||||
if (!empty($testData['EXPECT-EXCEPTION'])) {
|
||||
$expectResult = $testData['EXPECT-EXCEPTION'];
|
||||
|
@ -533,7 +586,7 @@ class InstallerTest extends TestCase
|
|||
die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
|
||||
}
|
||||
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expect, $expectResult);
|
||||
$tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectInstalled, $expectOutput, $expectOutputOptimized, $expect, $expectResult);
|
||||
}
|
||||
|
||||
return $tests;
|
||||
|
@ -557,6 +610,7 @@ class InstallerTest extends TestCase
|
|||
'EXPECT-LOCK' => false,
|
||||
'EXPECT-INSTALLED' => false,
|
||||
'EXPECT-OUTPUT' => false,
|
||||
'EXPECT-OUTPUT-OPTIMIZED' => false,
|
||||
'EXPECT-EXIT-CODE' => false,
|
||||
'EXPECT-EXCEPTION' => false,
|
||||
'EXPECT' => true,
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Composer\Util\Platform;
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
|
||||
|
@ -19,3 +21,5 @@ if (function_exists('date_default_timezone_set') && function_exists('date_defaul
|
|||
require __DIR__.'/../src/bootstrap.php';
|
||||
require __DIR__.'/../src/Composer/InstalledVersions.php';
|
||||
require __DIR__.'/Composer/Test/TestCase.php';
|
||||
|
||||
Platform::putEnv('COMPOSER_TESTS_ARE_RUNNING', '1');
|
||||
|
|
|
@ -75,13 +75,18 @@
|
|||
"count": 1
|
||||
},
|
||||
{
|
||||
"location": "Composer\\Test\\InstallerTest::testIntegration",
|
||||
"location": "Composer\\Test\\InstallerTest::testIntegrationWithRawPool",
|
||||
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
|
||||
"count": 1640
|
||||
"count": 1728
|
||||
},
|
||||
{
|
||||
"location": "Composer\\Test\\InstallerTest::testIntegrationWithPoolOptimizer",
|
||||
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
|
||||
"count": 1728
|
||||
},
|
||||
{
|
||||
"location": "Composer\\Test\\Package\\Archiver\\ArchivableFilesFinderTest::testManualExcludes",
|
||||
"message": "Return type of Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator::accept() should either be compatible with FilterIterator::accept(): bool, or the #[\\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue