Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Do not optimise away packages due to a requirement by a locked package that will be uninstalled #10405

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Composer/DependencyResolver/PoolOptimizer.php
Expand Up @@ -418,6 +418,20 @@ private function optimizeImpossiblePackagesAway(Request $request, Pool $pool)
}

foreach ($request->getLockedPackages() as $package) {
// If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements
// In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still
$isUnusedPackage = true;
foreach ($package->getNames(false) as $packageName) {
if (isset($this->requireConstraintsPerPackage[$packageName])) {
$isUnusedPackage = false;
break;
}
}

if ($isUnusedPackage) {
continue;
}

foreach ($package->getRequires() as $link) {
$require = $link->getTarget();
if (!isset($packageIndex[$require])) {
Expand Down
@@ -0,0 +1,60 @@
--TEST--
Do not load packages into the pool that cannot meet the fixed/locked requirements, when a loose requirement is encountered during update
(The locked replacer/pkg should restrict dependencies even though it is only referenced by its replaced name)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "*",
"replacer/dep": "*"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.0.0"},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}, "require": {"replacer/dep": "1.*"}},
{"name": "replacer/dep", "version": "1.0.0"}
],
"allowList": [
"second/pkg",
"replacer/dep"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "first/pkg", "version": "1.1.0", "require": {"replaced/pkg": "2.0.0"}},
{"name": "second/pkg", "version": "1.0.0"},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}, "require": {"replacer/dep": "1.*"}},
{"name": "replacer/pkg", "version": "1.1.0", "replace": {"replaced/pkg": "2.0.0"}, "require": {"replacer/dep": "1.*"}},
{"name": "replacer/pkg", "version": "1.2.0", "replace": {"replaced/pkg": "3.0.0"}, "require": {"replacer/dep": "1.*"}},
{"name": "replacer/dep", "version": "1.0.0"},
{"name": "replacer/dep", "version": "1.0.1"},
{"name": "replacer/dep", "version": "2.0.0"}
]
]

--EXPECT--
[
"first/pkg-1.0.0.0 (locked)",
"replacer/pkg-1.0.0.0 (locked)",
"second/pkg-1.0.0.0",
"replacer/dep-1.0.0.0",
"replacer/dep-1.0.1.0",
"replacer/dep-2.0.0.0"
]

--EXPECT-OPTIMIZED--
[
"first/pkg-1.0.0.0 (locked)",
"replacer/pkg-1.0.0.0 (locked)",
"second/pkg-1.0.0.0",
"replacer/dep-1.0.1.0"
]
@@ -0,0 +1,58 @@
--TEST--
When filtering packages from the pool that cannot meet the fixed/locked requirements, ensure that the requirements for a package that is not required anywhere is not used to filter, as it will be ultimately be removed.
(Variant where the requirement is on a provided package, the locked third/pkg must not restrict the provider)
(NOTE: We are not optimising this scenario currently)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "1.1.0",
"provider/pkg": "*"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "^1.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"provided/pkg": "1.0.0"}},
{"name": "provider/pkg", "version": "1.0.0", "provide": {"provided/pkg": "1.0.0"}}
],
"allowList": [
"first/pkg",
"provider/pkg"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "*"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.1.0", "require": {"provided/pkg": "2.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"provided/pkg": "1.0.0"}},
{"name": "provider/pkg", "version": "1.0.0", "provide": {"provided/pkg": "1.0.0"}},
{"name": "provider/pkg", "version": "2.0.0", "provide": {"provided/pkg": "2.0.0"}}
]
]

--EXPECT--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"provider/pkg-1.0.0.0",
"provider/pkg-2.0.0.0",
"second/pkg-1.1.0.0"
]

--EXPECT-OPTIMIZED--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"provider/pkg-1.0.0.0",
"provider/pkg-2.0.0.0",
"second/pkg-1.1.0.0"
]
@@ -0,0 +1,57 @@
--TEST--
When filtering packages from the pool that cannot meet the fixed/locked requirements, ensure that the requirements for a package that is not required anywhere is not used to filter, as it will be ultimately be removed.
(Variant where the requirement is on a replaced package, the locked third/pkg must not restrict the replacer)
(NOTE: We are not optimising this scenario currently)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "1.1.0",
"replacer/pkg": "*"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "^1.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}}
],
"allowList": [
"first/pkg"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "*"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.1.0", "require": {"replaced/pkg": "2.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}},
{"name": "replacer/pkg", "version": "2.0.0", "replace": {"replaced/pkg": "2.0.0"}}
]
]

--EXPECT--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"second/pkg-1.1.0.0",
"replacer/pkg-1.0.0.0",
"replacer/pkg-2.0.0.0"
]

--EXPECT-OPTIMIZED--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"second/pkg-1.1.0.0",
"replacer/pkg-1.0.0.0",
"replacer/pkg-2.0.0.0"
]
@@ -0,0 +1,55 @@
--TEST--
When filtering packages from the pool that cannot meet the fixed/locked requirements, ensure that the requirements for a package that is not required anywhere is not used to filter, as it will be ultimately be removed.
(The locked third/pkg is not required by any package so will be removed, so should not restrict the versions of fourth/pkg)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "1.1.0"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "^1.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"fourth/pkg": "1.0.0"}},
{"name": "fourth/pkg", "version": "1.0.0"}
],
"allowList": [
"first/pkg"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"second/pkg": "*"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"third/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.1.0", "require": {"fourth/pkg": "2.0.0"}},
{"name": "third/pkg", "version": "1.0.0", "require": {"fourth/pkg": "1.0.0"}},
{"name": "fourth/pkg", "version": "1.0.0"},
{"name": "fourth/pkg", "version": "2.0.0"}
]
]

--EXPECT--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"second/pkg-1.1.0.0",
"fourth/pkg-1.0.0.0",
"fourth/pkg-2.0.0.0"
]

--EXPECT-OPTIMIZED--
[
"third/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"second/pkg-1.1.0.0",
"fourth/pkg-1.0.0.0",
"fourth/pkg-2.0.0.0"
]
@@ -0,0 +1,59 @@
--TEST--
Do not load packages into the pool that cannot meet the fixed/locked requirements, when a loose requirement is encountered during update
(Variant where requirement is on a provided package, the locked second package should restrict the provider to those that provide < 3.0.0)
(NOTE: We are not optimising this scenario currently)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "*",
"provider/pkg": "*"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"provided/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"provided/pkg": "< 3.0.0"}},
{"name": "provider/pkg", "version": "1.0.0", "provide": {"provided/pkg": "1.0.0"}}
],
"allowList": [
"first/pkg",
"provider/pkg"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"provided/pkg": "1.0.0"}},
{"name": "first/pkg", "version": "1.1.0", "require": {"provided/pkg": "2.0.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"provided/pkg": "*"}},
{"name": "provider/pkg", "version": "1.0.0", "provide": {"provided/pkg": "1.0.0"}},
{"name": "provider/pkg", "version": "1.1.0", "provide": {"provided/pkg": "2.0.0"}},
{"name": "provider/pkg", "version": "1.2.0", "provide": {"provided/pkg": "3.0.0"}}
]
]

--EXPECT--
[
"second/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"first/pkg-1.1.0.0",
"provider/pkg-1.0.0.0",
"provider/pkg-1.1.0.0",
"provider/pkg-1.2.0.0"
]

--EXPECT-OPTIMIZED--
[
"second/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"first/pkg-1.1.0.0",
"provider/pkg-1.0.0.0",
"provider/pkg-1.1.0.0",
"provider/pkg-1.2.0.0"
]
@@ -0,0 +1,58 @@
--TEST--
Do not load packages into the pool that cannot meet the fixed/locked requirements, when a loose requirement is encountered during update
(Variant where requirement is on a replaced package, the locked second package should restrict the replacer to those that replace < 3.0.0)
(NOTE: We are not optimising this scenario currently)

--REQUEST--
{
"require": {
"first/pkg": "*",
"second/pkg": "*",
"replacer/pkg": "*"
},
"locked": [
{"name": "first/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"replaced/pkg": "< 3.0.0"}},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}}
],
"allowList": [
"first/pkg"
],
"allowTransitiveDeps": true
}

--FIXED--
[
]

--PACKAGE-REPOS--
[
[
{"name": "first/pkg", "version": "1.0.0", "require": {"replaced/pkg": "1.0.0"}},
{"name": "first/pkg", "version": "1.1.0", "require": {"replaced/pkg": "2.0.0"}},
{"name": "second/pkg", "version": "1.0.0", "require": {"replaced/pkg": "*"}},
{"name": "replacer/pkg", "version": "1.0.0", "replace": {"replaced/pkg": "1.0.0"}},
{"name": "replacer/pkg", "version": "1.1.0", "replace": {"replaced/pkg": "2.0.0"}},
{"name": "replacer/pkg", "version": "1.2.0", "replace": {"replaced/pkg": "3.0.0"}}
]
]

--EXPECT--
[
"second/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"first/pkg-1.1.0.0",
"replacer/pkg-1.0.0.0",
"replacer/pkg-1.1.0.0",
"replacer/pkg-1.2.0.0"
]

--EXPECT-OPTIMIZED--
[
"second/pkg-1.0.0.0 (locked)",
"first/pkg-1.0.0.0",
"first/pkg-1.1.0.0",
"replacer/pkg-1.0.0.0",
"replacer/pkg-1.1.0.0",
"replacer/pkg-1.2.0.0"
]
@@ -1,5 +1,6 @@
--TEST--
Do not load packages into the pool that cannot meet the fixed/locked requirements, when a loose requirement is encountered during update
(The locked root/req package should restrict dep/dep to only 2.* versions)

--REQUEST--
{
Expand Down