1
0
Fork 0

Merge pull request #9477 from naderman/fix-alias-conflicts

Explicit conflicts should conflict with aliases of packages too
pull/9532/head
Jordi Boggiano 2020-11-26 13:59:57 +01:00 committed by GitHub
commit d00edab884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 256 additions and 27 deletions

View File

@ -197,16 +197,21 @@ class Decisions implements \Iterator, \Countable
} }
} }
public function __toString() public function toString(Pool $pool = null)
{ {
$decisionMap = $this->decisionMap; $decisionMap = $this->decisionMap;
ksort($decisionMap); ksort($decisionMap);
$str = '['; $str = '[';
foreach ($decisionMap as $packageId => $level) { foreach ($decisionMap as $packageId => $level) {
$str .= $packageId.':'.$level.','; $str .= (($pool) ? $pool->literalToPackage($packageId) : $packageId).':'.$level.',';
} }
$str .= ']'; $str .= ']';
return $str; return $str;
} }
public function __toString()
{
return $this->toString();
}
} }

View File

@ -34,7 +34,7 @@ abstract class Rule
const RULE_PACKAGE_SAME_NAME = 10; const RULE_PACKAGE_SAME_NAME = 10;
const RULE_LEARNED = 12; const RULE_LEARNED = 12;
const RULE_PACKAGE_ALIAS = 13; const RULE_PACKAGE_ALIAS = 13;
const RULE_PACKAGE_ROOT_ALIAS = 14; const RULE_PACKAGE_INVERSE_ALIAS = 14;
// bitfield defs // bitfield defs
const BITFIELD_TYPE = 0; const BITFIELD_TYPE = 0;
@ -311,22 +311,26 @@ abstract class Rule
return 'Conclusion: '.$ruleText.$learnedString; return 'Conclusion: '.$ruleText.$learnedString;
case self::RULE_PACKAGE_ALIAS: case self::RULE_PACKAGE_ALIAS:
case self::RULE_PACKAGE_ROOT_ALIAS: $aliasPackage = $pool->literalToPackage($literals[0]);
if ($this->getReason() === self::RULE_PACKAGE_ALIAS) {
$aliasPackage = $pool->literalToPackage($literals[0]);
$otherLiteral = 1;
} else {
// root alias rules work the other way around
$aliasPackage = $pool->literalToPackage($literals[1]);
$otherLiteral = 0;
}
// avoid returning content like "9999999-dev is an alias of dev-master" as it is useless // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) { if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
return ''; return '';
} }
$package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[$otherLiteral])); $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));
return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.'; return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.';
case self::RULE_PACKAGE_INVERSE_ALIAS:
// inverse alias rules work the other way around than above
$aliasPackage = $pool->literalToPackage($literals[1]);
// avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
return '';
}
$package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.';
default: default:
$ruleText = ''; $ruleText = '';
foreach ($literals as $i => $literal) { foreach ($literals as $i => $literal) {

View File

@ -164,10 +164,8 @@ class RuleSetGenerator
$workQueue->enqueue($package->getAliasOf()); $workQueue->enqueue($package->getAliasOf());
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($package->getAliasOf()), Rule::RULE_PACKAGE_ALIAS, $package)); $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($package->getAliasOf()), Rule::RULE_PACKAGE_ALIAS, $package));
// root aliases must be installed with their main package, so create a rule the other way around as well // aliases must be installed with their main package, so create a rule the other way around as well
if ($package->isRootPackageAlias()) { $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), array($package), Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf()));
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), array($package), Rule::RULE_PACKAGE_ROOT_ALIAS, $package->getAliasOf()));
}
// if alias package has no self.version requires, its requirements do not // if alias package has no self.version requires, its requirements do not
// need to be added as the aliased package processing will take care of it // need to be added as the aliased package processing will take care of it
@ -197,6 +195,7 @@ class RuleSetGenerator
/** @var PackageInterface $package */ /** @var PackageInterface $package */
foreach ($this->addedMap as $package) { foreach ($this->addedMap as $package) {
foreach ($package->getConflicts() as $link) { foreach ($package->getConflicts() as $link) {
// even if conlict ends up being with an alias, there would be at least one actual package by this name
if (!isset($this->addedPackagesByNames[$link->getTarget()])) { if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
continue; continue;
} }
@ -205,10 +204,14 @@ class RuleSetGenerator
continue; continue;
} }
/** @var PackageInterface $possibleConflict */ $conflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
if ($this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint())) { foreach ($conflicts as $conflict) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link)); // define the conflict rule for regular packages, for alias packages it's only needed if the name
// matches the conflict exactly, otherwise the name match is by provide/replace which means the
// package which this is an alias of will conflict anyway, so no need to create additional rules
if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) {
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
} }
} }
} }
@ -266,9 +269,13 @@ class RuleSetGenerator
protected function addRulesForRootAliases($ignorePlatformReqs) protected function addRulesForRootAliases($ignorePlatformReqs)
{ {
foreach ($this->pool->getPackages() as $package) { foreach ($this->pool->getPackages() as $package) {
// ensure that rules for root alias packages get loaded even if the root alias itself isn't required // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded
// otherwise a package could be installed without its root alias which leads to unexpected behavior // even if the alias itself isn't required, otherwise a package could be installed without its alias which
if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { // leads to unexpected behavior
if (!isset($this->addedMap[$package->id]) &&
$package instanceof AliasPackage &&
($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id]))
) {
$this->addRulesForPackage($package, $ignorePlatformReqs); $this->addRulesForPackage($package, $ignorePlatformReqs);
} }
} }

View File

@ -840,6 +840,7 @@ class SolverTest extends TestCase
array('job' => 'install', 'package' => $packageB), array('job' => 'install', 'package' => $packageB),
array('job' => 'markAliasInstalled', 'package' => $packageBAlias), array('job' => 'markAliasInstalled', 'package' => $packageBAlias),
array('job' => 'install', 'package' => $packageC), array('job' => 'install', 'package' => $packageC),
array('job' => 'markAliasInstalled', 'package' => $packageCAlias),
)); ));
} }

View File

@ -0,0 +1,38 @@
--TEST--
Test that a root package conflict with a branch alias leads to an error, even if the branch alias isn't required.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "some/dep", "version": "1.0.0" },
{ "name": "some/dep", "version": "1.1.0" },
{ "name": "some/dep", "version": "1.2.0" },
{ "name": "some/dep", "version": "dev-main", "extra": {"branch-alias": {"dev-main": "1.3.x-dev"} } },
{ "name": "some/dep", "version": "1.2.x-dev" }
]
}
],
"require": {
"some/dep": "dev-main"
},
"conflict": {
"some/dep": ">=1.3"
}
}
--RUN--
update
--EXPECT-EXIT-CODE--
2
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- __root__ is present at version 1.0.0+no-version-set and cannot be modified by Composer
- some/dep 1.3.x-dev is an alias of some/dep dev-main and must be installed with it.
- __root__ 1.0.0+no-version-set conflicts with some/dep 1.3.x-dev.
- Root composer.json requires some/dep dev-main -> satisfiable by some/dep[dev-main].
--EXPECT--

View File

@ -0,0 +1,54 @@
--TEST--
Test that conflict with a branch alias in the lock file leads to an error on install from lock, even if the branch alias was removed on the remote end.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "some/dep", "version": "1.0.0" },
{ "name": "some/dep", "version": "1.1.0" },
{ "name": "some/dep", "version": "1.2.0" },
{ "name": "some/dep", "version": "dev-main" },
{ "name": "some/dep", "version": "1.2.x-dev" },
{ "name": "conflictor/foo", "version": "1.0.0", "conflict": { "some/dep": ">=1.3" } }
]
}
],
"require": {
"some/dep": "dev-main",
"conflictor/foo": "1.0.0"
}
}
--LOCK--
{
"packages": [
{ "name": "conflictor/foo", "version": "1.0.0", "conflict": { "some/dep": ">=1.3" }, "type": "library" },
{ "name": "some/dep", "version": "dev-main", "extra": {"branch-alias": {"dev-main": "1.3.x-dev"} }, "type": "library" }
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"some/dep": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--RUN--
install
--EXPECT-EXIT-CODE--
2
--EXPECT-OUTPUT--
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Your lock file does not contain a compatible set of packages. Please run composer update.
Problem 1
- conflictor/foo is locked to version 1.0.0 and an update of this package was not requested.
- conflictor/foo 1.0.0 conflicts with some/dep 1.3.x-dev.
- some/dep is locked to version 1.3.x-dev and an update of this package was not requested.
--EXPECT--

View File

@ -0,0 +1,38 @@
--TEST--
Test that conflict of a dependency with a branch alias of another dependency is not ignored, even if the alias is not required for installation.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "some/dep", "version": "1.0.0" },
{ "name": "some/dep", "version": "1.1.0" },
{ "name": "some/dep", "version": "1.2.0" },
{ "name": "some/dep", "version": "dev-main", "extra": {"branch-alias": {"dev-main": "1.3.x-dev"} } },
{ "name": "some/dep", "version": "1.2.x-dev" },
{ "name": "conflictor/foo", "version": "1.0.0", "conflict": { "some/dep": ">=1.3" } }
]
}
],
"require": {
"some/dep": "dev-main",
"conflictor/foo": "1.0.0"
}
}
--RUN--
update
--EXPECT-EXIT-CODE--
2
--EXPECT-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 some/dep dev-main -> satisfiable by some/dep[dev-main].
- conflictor/foo 1.0.0 conflicts with some/dep 1.3.x-dev.
- some/dep 1.3.x-dev is an alias of some/dep dev-main and must be installed with it.
- Root composer.json requires conflictor/foo 1.0.0 -> satisfiable by conflictor/foo[1.0.0].
--EXPECT--

View File

@ -0,0 +1,43 @@
--TEST--
Test that conflict on a branch alias is respected
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "some/dep", "version": "1.0.0" },
{ "name": "some/dep", "version": "1.1.0" },
{ "name": "some/dep", "version": "1.2.0" },
{ "name": "some/dep", "version": "dev-main", "extra": {"branch-alias": {"dev-main": "1.3.x-dev"} } },
{ "name": "some/dep", "version": "1.2.x-dev" }
]
}
],
"require": {
"some/dep": "^1.0@dev"
},
"conflict": {
"some/dep": ">=1.3"
}
}
--RUN--
update
--EXPECT-LOCK--
{
"packages": [
{ "name": "some/dep", "version": "1.2.x-dev", "type": "library" }
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"some/dep": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--EXPECT--
Installing some/dep (1.2.x-dev)

View File

@ -1,5 +1,5 @@
--TEST-- --TEST--
Test that a conflict against >=5 does not include dev-master or other dev-x Test that a conflict against >=5 does not include the default branch if it has a branch alias defined.
--COMPOSER-- --COMPOSER--
{ {
"repositories": [ "repositories": [
@ -7,7 +7,7 @@ Test that a conflict against >=5 does not include dev-master or other dev-x
"type": "package", "type": "package",
"package": [ "package": [
{ "name": "conflicter/pkg", "version": "1.0.0", "conflict": { "victim/pkg": ">=5", "victim/pkg2": ">=5" } }, { "name": "conflicter/pkg", "version": "1.0.0", "conflict": { "victim/pkg": ">=5", "victim/pkg2": ">=5" } },
{ "name": "victim/pkg", "version": "dev-master", "default-branch": true }, { "name": "victim/pkg", "version": "dev-master", "default-branch": true, "extra": { "branch-alias": { "dev-master": "2.x-dev" } } },
{ "name": "victim/pkg2", "version": "dev-foo" } { "name": "victim/pkg2", "version": "dev-foo" }
] ]
} }
@ -27,5 +27,5 @@ update
--EXPECT-- --EXPECT--
Installing conflicter/pkg (1.0.0) Installing conflicter/pkg (1.0.0)
Installing victim/pkg (dev-master) Installing victim/pkg (dev-master)
Marking victim/pkg (9999999-dev) as installed, alias of victim/pkg (dev-master) Marking victim/pkg (2.x-dev) as installed, alias of victim/pkg (dev-master)
Installing victim/pkg2 (dev-foo) Installing victim/pkg2 (dev-foo)

View File

@ -0,0 +1,39 @@
--TEST--
Test that a conflict against >=5 includes the default branch if it has no branch alias defined (and then uses the default 9999999-dev alias).
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "conflicter/pkg", "version": "1.0.0", "conflict": { "victim/pkg": ">=5", "victim/pkg2": ">=5" } },
{ "name": "victim/pkg", "version": "dev-master", "default-branch": true },
{ "name": "victim/pkg2", "version": "dev-foo" }
]
}
],
"require": {
"conflicter/pkg": "1.0.0",
"victim/pkg": "*",
"victim/pkg2": "*"
},
"minimum-stability": "dev"
}
--RUN--
update
--EXPECT-EXIT-CODE--
2
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- conflicter/pkg 1.0.0 conflicts with victim/pkg dev-master.
- Root composer.json requires conflicter/pkg 1.0.0 -> satisfiable by conflicter/pkg[1.0.0].
- Root composer.json requires victim/pkg * -> satisfiable by victim/pkg[dev-master].
--EXPECT--