Merge pull request #9477 from naderman/fix-alias-conflicts
Explicit conflicts should conflict with aliases of packages toopull/9532/head
commit
d00edab884
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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--
|
|
@ -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--
|
|
@ -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--
|
|
@ -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)
|
|
@ -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)
|
|
@ -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--
|
Loading…
Reference in New Issue