pull/10809/head
parent
d971f2e37e
commit
556450b15b
|
@ -21,6 +21,7 @@ use Composer\Pcre\Preg;
|
|||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Package\Link;
|
||||
|
@ -144,20 +145,13 @@ EOT
|
|||
}
|
||||
}
|
||||
|
||||
$rootPackage = $composer->getPackage();
|
||||
$rootRequires = $rootPackage->getRequires();
|
||||
$rootDevRequires = $rootPackage->getDevRequires();
|
||||
$parser = new VersionParser;
|
||||
$temporaryConstraints = [];
|
||||
foreach ($reqs as $package => $constraint) {
|
||||
if (isset($rootRequires[$package])) {
|
||||
$rootRequires[$package] = $this->appendConstraintToLink($rootRequires[$package], $constraint);
|
||||
} elseif (isset($rootDevRequires[$package])) {
|
||||
$rootDevRequires[$package] = $this->appendConstraintToLink($rootDevRequires[$package], $constraint);
|
||||
} else {
|
||||
throw new \UnexpectedValueException('Only root package requirements can receive temporary constraints and '.$package.' is not one');
|
||||
$temporaryConstraints[strtolower($package)] = $parser->parseConstraints($constraint);
|
||||
}
|
||||
}
|
||||
$rootPackage->setRequires($rootRequires);
|
||||
$rootPackage->setDevRequires($rootDevRequires);
|
||||
|
||||
$rootPackage = $composer->getPackage();
|
||||
$rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences()));
|
||||
$rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags()));
|
||||
|
||||
|
@ -166,9 +160,9 @@ EOT
|
|||
}
|
||||
|
||||
if ($input->getOption('root-reqs')) {
|
||||
$requires = array_keys($rootRequires);
|
||||
$requires = array_keys($rootPackage->getRequires());
|
||||
if (!$input->getOption('no-dev')) {
|
||||
$requires = array_merge($requires, array_keys($rootDevRequires));
|
||||
$requires = array_merge($requires, array_keys($rootPackage->getDevRequires()));
|
||||
}
|
||||
|
||||
if (!empty($packages)) {
|
||||
|
@ -232,6 +226,7 @@ EOT
|
|||
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
|
||||
->setPreferStable($input->getOption('prefer-stable'))
|
||||
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||
->setTemporaryConstraints($temporaryConstraints)
|
||||
;
|
||||
|
||||
if ($input->getOption('no-plugins')) {
|
||||
|
@ -307,25 +302,4 @@ EOT
|
|||
|
||||
throw new \RuntimeException('Installation aborted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $constraint
|
||||
* @return Link
|
||||
*/
|
||||
private function appendConstraintToLink(Link $link, string $constraint): Link
|
||||
{
|
||||
$parser = new VersionParser;
|
||||
$oldPrettyString = $link->getConstraint()->getPrettyString();
|
||||
$newConstraint = MultiConstraint::create(array($link->getConstraint(), $parser->parseConstraints($constraint)));
|
||||
$newConstraint->setPrettyString($oldPrettyString.', '.$constraint);
|
||||
|
||||
return new Link(
|
||||
$link->getSource(),
|
||||
$link->getTarget(),
|
||||
$newConstraint,
|
||||
/** @phpstan-ignore-next-line */
|
||||
$link->getDescription(),
|
||||
$link->getPrettyConstraint() . ', ' . $constraint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,10 @@ class PoolBuilder
|
|||
* @phpstan-var array<string, string>
|
||||
*/
|
||||
private $rootReferences;
|
||||
/**
|
||||
* @var array<string, ConstraintInterface>
|
||||
*/
|
||||
private $temporaryConstraints;
|
||||
/**
|
||||
* @var ?EventDispatcher
|
||||
*/
|
||||
|
@ -142,8 +146,9 @@ class PoolBuilder
|
|||
* @phpstan-param array<string, array<string, array{alias: string, alias_normalized: string}>> $rootAliases
|
||||
* @param string[] $rootReferences an array of package name => source reference
|
||||
* @phpstan-param array<string, string> $rootReferences
|
||||
* @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
|
||||
*/
|
||||
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, EventDispatcher $eventDispatcher = null, PoolOptimizer $poolOptimizer = null)
|
||||
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, EventDispatcher $eventDispatcher = null, PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = [])
|
||||
{
|
||||
$this->acceptableStabilities = $acceptableStabilities;
|
||||
$this->stabilityFlags = $stabilityFlags;
|
||||
|
@ -152,6 +157,7 @@ class PoolBuilder
|
|||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->poolOptimizer = $poolOptimizer;
|
||||
$this->io = $io;
|
||||
$this->temporaryConstraints = $temporaryConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,24 +240,28 @@ class PoolBuilder
|
|||
$this->loadPackagesMarkedForLoading($request, $repositories);
|
||||
}
|
||||
|
||||
if (\count($this->temporaryConstraints) > 0) {
|
||||
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) {
|
||||
$constraint = new Constraint('==', $package->getVersion());
|
||||
$aliasedPackages = array($i => $package);
|
||||
if (!isset($this->temporaryConstraints[$package->getName()]) || $package instanceof AliasPackage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = $this->temporaryConstraints[$package->getName()];
|
||||
$packageAndAliases = array($i => $package);
|
||||
if (isset($this->aliasMap[spl_object_hash($package)])) {
|
||||
$aliasedPackages += $this->aliasMap[spl_object_hash($package)];
|
||||
$packageAndAliases += $this->aliasMap[spl_object_hash($package)];
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($aliasedPackages as $packageOrAlias) {
|
||||
foreach ($packageAndAliases as $packageOrAlias) {
|
||||
if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
foreach ($aliasedPackages as $index => $packageOrAlias) {
|
||||
foreach ($packageAndAliases as $index => $packageOrAlias) {
|
||||
unset($this->packages[$index]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,6 +294,16 @@ class Problem
|
|||
}
|
||||
}
|
||||
|
||||
$tempReqs = $repositorySet->getTemporaryConstraints();
|
||||
if (isset($tempReqs[$packageName])) {
|
||||
$filtered = array_filter($packages, function ($p) use ($tempReqs, $packageName): bool {
|
||||
return $tempReqs[$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, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$packageName.':'.$tempReqs[$packageName]->getPrettyString().').');
|
||||
}
|
||||
}
|
||||
|
||||
if ($lockedPackage) {
|
||||
$fixedConstraint = new Constraint('==', $lockedPackage->getVersion());
|
||||
$filtered = array_filter($packages, function ($p) use ($fixedConstraint): bool {
|
||||
|
|
|
@ -60,6 +60,7 @@ use Composer\Repository\RepositoryInterface;
|
|||
use Composer\Repository\RepositoryManager;
|
||||
use Composer\Repository\LockArrayRepository;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Util\Platform;
|
||||
|
||||
/**
|
||||
|
@ -189,6 +190,9 @@ class Installer
|
|||
*/
|
||||
protected $additionalFixedRepository;
|
||||
|
||||
/** @var array<string, ConstraintInterface> */
|
||||
protected $temporaryConstraints = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -837,7 +841,7 @@ class Installer
|
|||
|
||||
$stabilityFlags[$this->package->getName()] = BasePackage::$stabilities[VersionParser::parseStability($this->package->getVersion())];
|
||||
|
||||
$repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires);
|
||||
$repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints);
|
||||
$repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage));
|
||||
$repositorySet->addRepository($platformRepo);
|
||||
if ($this->additionalFixedRepository) {
|
||||
|
@ -1065,6 +1069,14 @@ class Installer
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ConstraintInterface> $constraints
|
||||
*/
|
||||
public function setTemporaryConstraints(array $constraints): void
|
||||
{
|
||||
$this->temporaryConstraints = $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to run in drymode or not
|
||||
*
|
||||
|
|
|
@ -73,6 +73,11 @@ class RepositorySet
|
|||
*/
|
||||
private $rootRequires;
|
||||
|
||||
/**
|
||||
* @var array<string, ConstraintInterface>
|
||||
*/
|
||||
private $temporaryConstraints;
|
||||
|
||||
/** @var bool */
|
||||
private $locked = false;
|
||||
/** @var bool */
|
||||
|
@ -92,8 +97,9 @@ class RepositorySet
|
|||
* @phpstan-param array<string, string> $rootReferences
|
||||
* @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package
|
||||
* @phpstan-param array<string, ConstraintInterface> $rootRequires
|
||||
* @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
|
||||
*/
|
||||
public function __construct(string $minimumStability = 'stable', array $stabilityFlags = array(), array $rootAliases = array(), array $rootReferences = array(), array $rootRequires = array())
|
||||
public function __construct(string $minimumStability = 'stable', array $stabilityFlags = array(), array $rootAliases = array(), array $rootReferences = array(), array $rootRequires = array(), array $temporaryConstraints = [])
|
||||
{
|
||||
$this->rootAliases = self::getRootAliasesPerPackage($rootAliases);
|
||||
$this->rootReferences = $rootReferences;
|
||||
|
@ -111,6 +117,8 @@ class RepositorySet
|
|||
unset($this->rootRequires[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->temporaryConstraints = $temporaryConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,6 +140,14 @@ class RepositorySet
|
|||
return $this->rootRequires;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, ConstraintInterface> Runtime temporary constraints that will be used to filter packages
|
||||
*/
|
||||
public function getTemporaryConstraints(): array
|
||||
{
|
||||
return $this->temporaryConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a repository to this repository set
|
||||
*
|
||||
|
@ -247,7 +263,7 @@ class RepositorySet
|
|||
*/
|
||||
public function createPool(Request $request, IOInterface $io, EventDispatcher $eventDispatcher = null, PoolOptimizer $poolOptimizer = null): Pool
|
||||
{
|
||||
$poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer);
|
||||
$poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints);
|
||||
|
||||
foreach ($this->repositories as $repo) {
|
||||
if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Command;
|
||||
|
||||
use Composer\Test\TestCase;
|
||||
|
||||
class UpdateCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUpdates
|
||||
* @param array<mixed> $composerJson
|
||||
* @param array<mixed> $command
|
||||
*/
|
||||
public function testUpdate(array $composerJson, array $command, string $expected): void
|
||||
{
|
||||
$this->initTempComposer($composerJson);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(array_merge(['command' => 'update', '--dry-run' => true], $command));
|
||||
|
||||
$this->assertSame(trim($expected), trim($appTester->getDisplay()));
|
||||
}
|
||||
|
||||
public function provideUpdates(): \Generator
|
||||
{
|
||||
$rootDepAndTransitiveDep = [
|
||||
'repositories' => [
|
||||
'packages' => [
|
||||
'type' => 'package',
|
||||
'package' => [
|
||||
['name' => 'root/req', 'version' => '1.0.0', 'require' => ['dep/pkg' => '^1']],
|
||||
['name' => 'dep/pkg', 'version' => '1.0.0'],
|
||||
['name' => 'dep/pkg', 'version' => '1.0.1'],
|
||||
['name' => 'dep/pkg', 'version' => '1.0.2'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'require' => [
|
||||
'root/req' => '1.*',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'simple update' => [
|
||||
$rootDepAndTransitiveDep,
|
||||
[],
|
||||
<<<OUTPUT
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Lock file operations: 2 installs, 0 updates, 0 removals
|
||||
- Locking dep/pkg (1.0.2)
|
||||
- Locking root/req (1.0.0)
|
||||
Installing dependencies from lock file (including require-dev)
|
||||
Package operations: 2 installs, 0 updates, 0 removals
|
||||
- Installing dep/pkg (1.0.2)
|
||||
- Installing root/req (1.0.0)
|
||||
OUTPUT
|
||||
];
|
||||
|
||||
yield 'update with temporary constraint + --no-install' => [
|
||||
$rootDepAndTransitiveDep,
|
||||
['--with' => ['dep/pkg:1.0.0'], '--no-install' => true],
|
||||
<<<OUTPUT
|
||||
Loading composer repositories with package information
|
||||
Updating dependencies
|
||||
Lock file operations: 2 installs, 0 updates, 0 removals
|
||||
- Locking dep/pkg (1.0.0)
|
||||
- Locking root/req (1.0.0)
|
||||
OUTPUT
|
||||
];
|
||||
|
||||
yield 'update with temporary constraint failing resolution' => [
|
||||
$rootDepAndTransitiveDep,
|
||||
['--with' => ['dep/pkg:^2']],
|
||||
<<<OUTPUT
|
||||
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 root/req 1.* -> satisfiable by root/req[1.0.0].
|
||||
- root/req 1.0.0 requires dep/pkg ^1 -> found dep/pkg[1.0.0, 1.0.1, 1.0.2] but it conflicts with your temporary update constraint (dep/pkg:^2).
|
||||
OUTPUT
|
||||
];
|
||||
}
|
||||
}
|
|
@ -124,9 +124,16 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
|||
Platform::putEnv('COMPOSER_HOME', $dir.'/composer-home');
|
||||
Platform::putEnv('COMPOSER_DISABLE_XDEBUG_WARN', '1');
|
||||
|
||||
if ($composerJson === []) {
|
||||
$composerJson = new \stdClass;
|
||||
}
|
||||
if ($authJson === []) {
|
||||
$authJson = new \stdClass;
|
||||
}
|
||||
|
||||
chdir($dir);
|
||||
file_put_contents($dir.'/composer.json', JsonFile::encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT));
|
||||
file_put_contents($dir.'/auth.json', JsonFile::encode($authJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT));
|
||||
file_put_contents($dir.'/composer.json', JsonFile::encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
file_put_contents($dir.'/auth.json', JsonFile::encode($authJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue