1
0
Fork 0

Backport tests from #9538 and fix everything

pull/10280/head
Jordi Boggiano 2021-11-23 12:58:59 +01:00
parent 13b7527fca
commit 3242de2438
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
8 changed files with 439 additions and 30 deletions

View File

@ -99,7 +99,7 @@ class PoolBuilder
private $unacceptableFixedOrLockedPackages = array();
/** @var string[] */
private $updateAllowList = array();
/** @var array<string, string> */
/** @var array<string, array<PackageInterface>> */
private $skippedLoad = array();
/**
@ -158,9 +158,8 @@ class PoolBuilder
$request->lockPackage($lockedPackage);
$lockedName = $lockedPackage->getName();
// remember which packages we skipped loading remote content for in this partial update
$this->skippedLoad[$lockedName] = $lockedName;
foreach ($lockedPackage->getReplaces() as $link) {
$this->skippedLoad[$link->getTarget()] = $lockedName;
foreach ($lockedPackage->getNames() as $name) {
$this->skippedLoad[$name][] = $lockedPackage;
}
}
}
@ -422,12 +421,18 @@ class PoolBuilder
// looking at a package which needs to be updated we need to unlock the package we now know is a
// dependency of another package which we are trying to update, and then attempt to load it again
if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$require])) {
$skippedRootRequires = $this->getSkippedRootRequires($request, $require);
if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) {
$this->unlockPackage($request, $require);
$this->markPackageNameForLoading($request, $require, $linkConstraint);
} elseif (!isset($this->updateAllowWarned[$this->skippedLoad[$require]])) {
$this->updateAllowWarned[$this->skippedLoad[$require]] = true;
$this->io->writeError('<warning>Dependency "'.$this->skippedLoad[$require].'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
} else {
foreach ($skippedRootRequires as $rootRequire) {
if (!isset($this->updateAllowWarned[$rootRequire])) {
$this->updateAllowWarned[$rootRequire] = true;
$this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
}
}
}
}
} else {
@ -441,12 +446,18 @@ class PoolBuilder
foreach ($package->getReplaces() as $link) {
$replace = $link->getTarget();
if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) {
if ($request->getUpdateAllowTransitiveRootDependencies() || !$this->isRootRequire($request, $this->skippedLoad[$replace])) {
$skippedRootRequires = $this->getSkippedRootRequires($request, $replace);
if ($request->getUpdateAllowTransitiveRootDependencies() || !$skippedRootRequires) {
$this->unlockPackage($request, $replace);
$this->markPackageNameForLoading($request, $replace, $link->getConstraint());
} elseif ($this->isRootRequire($request, $replace) && !isset($this->updateAllowWarned[$replace])) {
$this->updateAllowWarned[$replace] = true;
$this->io->writeError('<warning>Dependency "'.$replace.'" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
} else {
foreach ($skippedRootRequires as $rootRequire) {
if (!isset($this->updateAllowWarned[$rootRequire])) {
$this->updateAllowWarned[$rootRequire] = true;
$this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
}
}
}
}
}
@ -466,6 +477,56 @@ class PoolBuilder
return isset($rootRequires[$name]);
}
/**
* @param string $name
* @return string[]
*/
private function getSkippedRootRequires(Request $request, $name)
{
if (!isset($this->skippedLoad[$name])) {
return array();
}
$rootRequires = $request->getRequires();
$matches = array();
if (isset($rootRequires[$name])) {
return array_map(function (PackageInterface $package) use ($name) {
if ($name !== $package->getName()) {
foreach ($package->getReplaces() as $link) {
if ($link->getTarget() === $name) {
return $package->getName() .' (via replace of '.$name.')';
}
}
return $package->getName() .' (via provide of '.$name.')';
}
return $package->getName();
}, $this->skippedLoad[$name]);
}
foreach ($this->skippedLoad[$name] as $providedBy) {
foreach ($providedBy->getNames() as $providedName) {
if (isset($rootRequires[$providedName])) {
if ($name !== $providedBy->getName()) {
foreach ($providedBy->getReplaces() as $link) {
if ($link->getTarget() === $name) {
$matches[] = $providedBy->getName() .' (via replace of '.$name.')';
continue 2;
}
}
$matches[] = $providedBy->getName() .' (via provide of '.$name.')';
continue;
}
$matches[] = $providedBy->getName();
}
}
}
return $matches;
}
/**
* Checks whether the update allow list allows this package in the lock file to be updated
*
@ -529,22 +590,23 @@ class PoolBuilder
*/
private function unlockPackage(Request $request, $name)
{
if (
foreach ($this->skippedLoad[$name] as $providedBy) {
$providedBy = $providedBy->getName();
// if we unfixed a replaced package name, we also need to unfix the replacer itself
$this->skippedLoad[$name] !== $name
// as long as it was not unfixed yet
&& isset($this->skippedLoad[$this->skippedLoad[$name]])
) {
$replacerName = $this->skippedLoad[$name];
$this->unlockPackage($request, $replacerName);
if ($providedBy !== $name && isset($this->skippedLoad[$providedBy])) {
if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $providedBy))) {
$this->unlockPackage($request, $providedBy);
if ($this->isRootRequire($request, $replacerName)) {
$this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint);
if ($this->isRootRequire($request, $providedBy)) {
$this->markPackageNameForLoading($request, $providedBy, new MatchAllConstraint);
} else {
foreach ($this->packages as $loadedPackage) {
$requires = $loadedPackage->getRequires();
if (isset($requires[$replacerName])) {
$this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint());
if (isset($requires[$providedBy])) {
$this->markPackageNameForLoading($request, $providedBy, $requires[$providedBy]->getConstraint());
}
}
}
}
}

View File

@ -1,5 +1,5 @@
--TEST--
Check that replacers from additional repositories are loaded when doing a partial update
Check that replacers from additional repositories are loaded when doing a partial update allowing all transitive deps
--REQUEST--
{
@ -15,7 +15,7 @@ Check that replacers from additional repositories are loaded when doing a partia
"allowList": [
"indirect/replacer"
],
"allowTransitiveDepsNoRootRequire": true
"allowTransitiveDeps": true
}
--FIXED--

View File

@ -0,0 +1,87 @@
--TEST--
Check that replacers/providers which replace/provide a root requirement do not get unlocked
--REQUEST--
{
"require": {
"base/package": "^1.0",
"base/package2": "^1.0",
"indirect/replacer": "^1.0"
},
"locked": [
{"name": "shared/dep", "version": "1.2.0", "id": 1},
{"name": "shared/dep2", "version": "1.2.0", "id": 2},
{"name": "indirect/replacer", "version": "1.2.0", "require": {"replacer/package": "^1.2", "provider/package": "^1.2"}, "id": 3},
{"name": "replacer/package", "version": "1.2.0", "require": {"shared/dep": "^1.1"}, "replace": {"base/package": "1.2.0"}, "id": 4},
{"name": "provider/package", "version": "1.2.0", "require": {"shared/dep2": "^1.1"}, "provide": {"base/package2": "1.2.0"}, "id": 5}
],
"allowList": [
"indirect/replacer"
],
"allowTransitiveDepsNoRootRequire": true
}
--FIXED--
[
]
--PACKAGE-REPOS--
[
[
{
"name": "base/package",
"version": "1.0.0",
"require": {
"shared/dep": "^1.2"
}
},
{
"name": "base/package2",
"version": "1.0.0",
"require": {
"shared/dep2": "^1.2"
}
},
{
"name": "shared/dep",
"version": "1.0.0"
},
{
"name": "shared/dep",
"version": "1.2.0"
},
{
"name": "shared/dep2",
"version": "1.0.0"
},
{
"name": "shared/dep2",
"version": "1.2.0"
},
{
"name": "indirect/replacer",
"version": "1.2.0",
"require": {
"replacer/package": "^1.2"
}
},
{
"name": "indirect/replacer",
"version": "1.0.0",
"require": {
"replacer/package": "^1.0"
}
}
]
]
--EXPECT--
[
1,
4,
5,
"base/package2-1.0.0.0",
"indirect/replacer-1.2.0.0",
"indirect/replacer-1.0.0.0",
"shared/dep2-1.2.0.0"
]

View File

@ -50,7 +50,7 @@ update b/b --with-dependencies
--EXPECT-OUTPUT--
Loading composer repositories with package information
<warning>Dependency "a/a" is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
<warning>Dependency a/a is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
Updating dependencies
Nothing to modify in lock file
Writing lock file

View File

@ -0,0 +1,65 @@
--TEST--
Ensure a partial update of a dependency updates dependencies which provide its requirements.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{"name": "root/req1", "version": "1.0.0", "require": {"virtual/pkg1": "1.*"}},
{"name": "root/req1", "version": "2.0.0", "require": {"virtual/pkg1": "2.*"}},
{"name": "root/req2", "version": "1.0.0", "require": {"dep/pkg1": "1.*", "dep/pkg2": "1.*"}},
{"name": "dep/pkg1", "version": "1.0.0", "provide": {"virtual/pkg1": "1.0.0"}},
{"name": "dep/pkg1", "version": "1.2.0", "provide": {"virtual/pkg1": "2.0.0"}},
{"name": "dep/pkg2", "version": "1.0.0", "provide": {"virtual/pkg1": "1.0.0"}},
{"name": "dep/pkg2", "version": "1.2.0", "provide": {"virtual/pkg1": "2.0.0"}}
]
}
],
"require": {
"root/req1": "*",
"root/req2": "*"
}
}
--LOCK--
{
"packages": [
{"name": "dep/pkg1", "version": "1.0.0", "type": "library", "provide": {"virtual/pkg1": "1.0.0"}},
{"name": "dep/pkg2", "version": "1.0.0", "type": "library", "provide": {"virtual/pkg1": "1.0.0"}},
{"name": "root/req1", "version": "1.0.0", "type": "library", "require": {"virtual/pkg1": "1.*"}},
{"name": "root/req2", "version": "1.0.0", "type": "library", "require": {"dep/pkg1": "1.*", "dep/pkg2": "1.*"}}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--RUN--
update root/req1 --with-dependencies
--EXPECT-LOCK--
{
"packages": [
{"name": "dep/pkg1", "version": "1.2.0", "type": "library", "provide": {"virtual/pkg1": "2.0.0"}},
{"name": "dep/pkg2", "version": "1.2.0", "type": "library", "provide": {"virtual/pkg1": "2.0.0"}},
{"name": "root/req1", "version": "2.0.0", "type": "library", "require": {"virtual/pkg1": "2.*"}},
{"name": "root/req2", "version": "1.0.0", "type": "library", "require": {"dep/pkg1": "1.*", "dep/pkg2": "1.*"}}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--EXPECT--
Installing dep/pkg1 (1.2.0)
Installing dep/pkg2 (1.2.0)
Installing root/req1 (2.0.0)
Installing root/req2 (1.0.0)

View File

@ -0,0 +1,62 @@
--TEST--
Ensure a partial update of a dependency updates dependencies which replace one of its requirements.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{"name": "root/req1", "version": "1.0.0", "require": {"replaced/pkg1": "1.*"}},
{"name": "root/req1", "version": "2.0.0", "require": {"replaced/pkg1": "2.*"}},
{"name": "root/req2", "version": "1.0.0", "require": {"dep/pkg1": "1.*"}},
{"name": "replaced/pkg1", "version": "1.0.0"},
{"name": "replaced/pkg1", "version": "2.0.0"},
{"name": "dep/pkg1", "version": "1.0.0", "replace": {"replaced/pkg1": "1.0.0"}},
{"name": "dep/pkg1", "version": "1.2.0", "replace": {"replaced/pkg1": "2.0.0"}}
]
}
],
"require": {
"root/req1": "*",
"root/req2": "*"
}
}
--LOCK--
{
"packages": [
{"name": "dep/pkg1", "version": "1.0.0", "type": "library", "replace": {"replaced/pkg1": "1.0.0"}},
{"name": "root/req1", "version": "1.0.0", "type": "library", "require": {"replaced/pkg1": "1.*"}},
{"name": "root/req2", "version": "1.0.0", "type": "library", "require": {"dep/pkg1": "1.*"}}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--RUN--
update root/req1 --with-dependencies
--EXPECT-LOCK--
{
"packages": [
{"name": "dep/pkg1", "version": "1.2.0", "type": "library", "replace": {"replaced/pkg1": "2.0.0"}},
{"name": "root/req1", "version": "2.0.0", "type": "library", "require": {"replaced/pkg1": "2.*"}},
{"name": "root/req2", "version": "1.0.0", "type": "library", "require": {"dep/pkg1": "1.*"}}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--EXPECT--
Installing dep/pkg1 (1.2.0)
Installing root/req1 (2.0.0)
Installing root/req2 (1.0.0)

View File

@ -0,0 +1,133 @@
--TEST--
Ensure that a partial update of a dependency does not conflict if the only way to proceed is using an old locked version.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{
"name": "update/pkg1", "version": "1.0.0", "require": {
"dep/pkg2": "*",
"root/pkg3": "*",
"root/provided-pkg4": "*",
"root/replaced-pkg8": "*"
}
},
{
"name": "update/pkg1", "version": "2.0.0", "require": {
"dep/pkg2": "*",
"root/pkg3": "*",
"root/provided-pkg4": "*",
"root/replaced-pkg8": "*"
}
},
{"name": "dep/pkg2", "version": "1.0.0"},
{"name": "dep/pkg2", "version": "2.0.0"},
{"name": "root/pkg3", "version": "1.0.0"},
{"name": "root/pkg3", "version": "2.0.0"},
{"name": "dep/pkg5", "version": "1.0.0", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "dep/pkg5", "version": "2.0.0", "provide": {"root/provided-pkg4": "2.0.0"}},
{"name": "dep/pkg6", "version": "1.0.0", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "dep/pkg6", "version": "2.0.0", "provide": {"root/provided-pkg4": "2.0.0"}},
{"name": "root/pkg7", "version": "1.0.0", "require": {"dep/pkg5": "*", "dep/pkg6": "*"}},
{"name": "dep/pkg9", "version": "1.0.0", "replace": {"root/replaced-pkg8": "1.0.0"}},
{"name": "dep/pkg9", "version": "2.0.0", "replace": {"root/replaced-pkg8": "2.0.0"}},
{"name": "root/replaced-pkg8", "version": "1.0.0"},
{"name": "root/replaced-pkg8", "version": "2.0.0"},
{"name": "root/pkg10", "version": "1.0.0", "require": {"dep/pkg9": "*"}}
]
}
],
"require": {
"update/pkg1": "*",
"root/pkg3": "*",
"root/provided-pkg4": "*",
"root/pkg7": "*",
"root/replaced-pkg8": "*",
"root/pkg10": "*"
}
}
--LOCK--
{
"packages": [
{
"name": "update/pkg1", "version": "1.0.0", "type": "library", "require": {
"dep/pkg2": "*",
"root/pkg3": "*",
"root/provided-pkg4": "*",
"root/replaced-pkg8": "*"
}
},
{"name": "dep/pkg2", "version": "1.0.0", "type": "library"},
{"name": "root/pkg3", "version": "1.0.0", "type": "library"},
{"name": "dep/pkg5", "version": "1.0.0", "type": "library", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "dep/pkg6", "version": "1.0.0", "type": "library", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "root/pkg7", "version": "1.0.0", "type": "library", "require": {"dep/pkg5": "*", "dep/pkg6": "*"}},
{"name": "dep/pkg9", "version": "1.0.0", "type": "library", "replace": {"root/replaced-pkg8": "1.0.0"}},
{"name": "root/pkg10", "version": "1.0.0", "type": "library", "require": {"dep/pkg9": "*"}}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--RUN--
update update/pkg1 --with-dependencies
--EXPECT-LOCK--
{
"packages": [
{"name": "dep/pkg2", "version": "2.0.0", "type": "library"},
{"name": "dep/pkg5", "version": "1.0.0", "type": "library", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "dep/pkg6", "version": "1.0.0", "type": "library", "provide": {"root/provided-pkg4": "1.0.0"}},
{"name": "dep/pkg9", "version": "1.0.0", "type": "library", "replace": {"root/replaced-pkg8": "1.0.0"}},
{"name": "root/pkg10", "version": "1.0.0", "type": "library", "require": {"dep/pkg9": "*"}},
{"name": "root/pkg3", "version": "1.0.0", "type": "library"},
{"name": "root/pkg7", "version": "1.0.0", "type": "library", "require": {"dep/pkg5": "*", "dep/pkg6": "*"}},
{
"name": "update/pkg1", "version": "2.0.0", "type": "library", "require": {
"dep/pkg2": "*",
"root/pkg3": "*",
"root/provided-pkg4": "*",
"root/replaced-pkg8": "*"
}
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--EXPECT-OUTPUT--
Loading composer repositories with package information
<warning>Dependency root/pkg3 is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
<warning>Dependency dep/pkg5 (via provide of root/provided-pkg4) is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
<warning>Dependency dep/pkg6 (via provide of root/provided-pkg4) is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
<warning>Dependency dep/pkg9 (via replace of root/replaced-pkg8) is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>
Updating dependencies
Lock file operations: 0 installs, 2 updates, 0 removals
- Upgrading dep/pkg2 (1.0.0 => 2.0.0)
- Upgrading update/pkg1 (1.0.0 => 2.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
Generating autoload files
--EXPECT--
Installing dep/pkg9 (1.0.0)
Installing root/pkg10 (1.0.0)
Installing dep/pkg6 (1.0.0)
Installing dep/pkg5 (1.0.0)
Installing root/pkg7 (1.0.0)
Installing root/pkg3 (1.0.0)
Installing dep/pkg2 (2.0.0)
Installing update/pkg1 (2.0.0)

View File

@ -77,12 +77,12 @@
{
"location": "Composer\\Test\\InstallerTest::testIntegrationWithRawPool",
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
"count": 1728
"count": 1776
},
{
"location": "Composer\\Test\\InstallerTest::testIntegrationWithPoolOptimizer",
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
"count": 1728
"count": 1776
},
{
"location": "Composer\\Test\\Package\\Archiver\\ArchivableFilesFinderTest::testManualExcludes",