From 7b183956d839bce9f6f5c122e2f15ba9e1fe304b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 12 Nov 2020 17:05:50 +0100 Subject: [PATCH] Make sure the root aliases always get installed when a package is updated, fixes #9448 --- .../DependencyResolver/LockTransaction.php | 4 + src/Composer/DependencyResolver/Pool.php | 5 ++ .../DependencyResolver/RuleSetGenerator.php | 15 +++- .../Test/DependencyResolver/SolverTest.php | 48 +++++++++++- .../Fixtures/installer/alias-in-lock.test | 8 +- .../Fixtures/installer/alias-in-lock2.test | 75 +++++++++++++++++++ .../installer/aliases-with-require-dev.test | 4 +- ...oot-alias-gets-loaded-for-locked-pkgs.test | 54 +++++++++++++ 8 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/alias-in-lock2.test create mode 100644 tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 3b0255304..7c0d19834 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -133,6 +133,10 @@ class LockTransaction extends Transaction } } + usort($usedAliases, function ($a, $b) { + return strcmp($a['package'], $b['package']); + }); + return $usedAliases; } } diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 511d2b427..195864599 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -55,6 +55,11 @@ class Pool implements \Countable } } + public function getPackages() + { + return $this->packages; + } + /** * Retrieves the package object for a given package id. * diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index ba3264857..b710f16cf 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -231,7 +231,7 @@ class RuleSetGenerator } // otherwise, looks like a bug - throw new \LogicException("Fixed package ".$package->getName()." ".$package->getVersion().($package instanceof AliasPackage ? " (alias)" : "")." was not added to solver pool."); + throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool."); } $this->addRulesForPackage($package, $ignorePlatformReqs); @@ -262,6 +262,17 @@ class RuleSetGenerator } } + protected function addRulesForRootAliases() + { + foreach ($this->pool->getPackages() as $package) { + // if it is a root alias, make sure that if the aliased version gets installed + // the alias must be installed too + if ($package instanceof AliasPackage && $package->isRootPackageAlias()) { + $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), array($package), Rule::RULE_PACKAGE_REQUIRES, $package->getAliasOf())); + } + } + } + /** * @param bool|array $ignorePlatformReqs */ @@ -277,6 +288,8 @@ class RuleSetGenerator $this->addRulesForRequest($request, $ignorePlatformReqs); + $this->addRulesForRootAliases(); + $this->addConflictRules($ignorePlatformReqs); // Remove references to packages diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 9845c62da..303e295cb 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -789,7 +789,7 @@ class SolverTest extends TestCase $this->checkSolverResult(array( array('job' => 'install', 'package' => $packageB), array('job' => 'install', 'package' => $packageA2), - array('job' => 'install', 'package' => $packageA2Alias), + array('job' => 'markAliasInstalled', 'package' => $packageA2Alias), )); } @@ -811,11 +811,40 @@ class SolverTest extends TestCase $this->checkSolverResult(array( array('job' => 'install', 'package' => $packageA), - array('job' => 'install', 'package' => $packageAAlias), + array('job' => 'markAliasInstalled', 'package' => $packageAAlias), array('job' => 'install', 'package' => $packageB), )); } + public function testInstallRootAliasesIfAliasOfIsInstalled() + { + // root aliased, required + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageAAlias = $this->getAliasPackage($packageA, '1.1')); + $packageAAlias->setRootPackageAlias(true); + // root aliased, not required, should still be installed as it is root alias + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageBAlias = $this->getAliasPackage($packageB, '1.1')); + $packageBAlias->setRootPackageAlias(true); + // regular alias, not required, alias should not be installed + $this->repo->addPackage($packageC = $this->getPackage('C', '1.0')); + $this->repo->addPackage($packageCAlias = $this->getAliasPackage($packageC, '1.1')); + + $this->reposComplete(); + + $this->request->requireName('A', $this->getVersionConstraint('==', '1.1')); + $this->request->requireName('B', $this->getVersionConstraint('==', '1.0')); + $this->request->requireName('C', $this->getVersionConstraint('==', '1.0')); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageA), + array('job' => 'markAliasInstalled', 'package' => $packageAAlias), + array('job' => 'install', 'package' => $packageB), + array('job' => 'markAliasInstalled', 'package' => $packageBAlias), + array('job' => 'install', 'package' => $packageC), + )); + } + /** * Tests for a bug introduced in commit 451bab1c2cd58e05af6e21639b829408ad023463 Solver.php line 554/523 * @@ -915,6 +944,11 @@ class SolverTest extends TestCase 'from' => $operation->getInitialPackage(), 'to' => $operation->getTargetPackage(), ); + } elseif (in_array($operation->getOperationType(), array('markAliasInstalled', 'markAliasUninstalled'))) { + $result[] = array( + 'job' => $operation->getOperationType(), + 'package' => $operation->getPackage(), + ); } else { $job = ('uninstall' === $operation->getOperationType() ? 'remove' : 'install'); $result[] = array( @@ -924,6 +958,16 @@ class SolverTest extends TestCase } } + $expectedReadable = array(); + foreach ($expected as $op) { + $expectedReadable[] = array_map('strval', $op); + } + $resultReadable = array(); + foreach ($result as $op) { + $resultReadable[] = array_map('strval', $op); + } + + $this->assertEquals($expectedReadable, $resultReadable); $this->assertEquals($expected, $result); } } diff --git a/tests/Composer/Test/Fixtures/installer/alias-in-lock.test b/tests/Composer/Test/Fixtures/installer/alias-in-lock.test index 25660566f..49cd1519f 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-in-lock.test +++ b/tests/Composer/Test/Fixtures/installer/alias-in-lock.test @@ -1,5 +1,5 @@ --TEST-- -Root-defined aliases end up in lock file only if required to solve deps +Root-defined aliases end up in lock file always on full update --COMPOSER-- { "repositories": [ @@ -50,6 +50,11 @@ update "version": "3.0.2.0", "alias": "3.0.3", "alias_normalized": "3.0.3.0" + },{ + "package": "a/aliased2", + "version": "3.0.2.0", + "alias": "3.0.3", + "alias_normalized": "3.0.3.0" }], "minimum-stability": "stable", "stability-flags": [], @@ -60,6 +65,7 @@ update } --EXPECT-- Installing a/aliased2 (3.0.2) +Marking a/aliased2 (3.0.3) as installed, alias of a/aliased2 (3.0.2) Installing a/aliased (3.0.2) Marking a/aliased (3.0.3) as installed, alias of a/aliased (3.0.2) Installing b/requirer (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test b/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test new file mode 100644 index 000000000..c781ae32f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/alias-in-lock2.test @@ -0,0 +1,75 @@ +--TEST-- +Newly defined root aliases end up in lock file only if the package is updated +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "a/aliased", "version": "3.0.2" + }, + { + "name": "a/aliased2", "version": "3.0.2" + } + ] + } + ], + "require": { + "a/aliased": "3.0.2 as 3.0.3", + "a/aliased2": "3.0.2 as 3.0.3" + } +} +--LOCK-- +{ + "packages": [ + { + "name": "a/aliased", "version": "3.0.2", + "type": "library" + }, + { + "name": "a/aliased2", "version": "3.0.2", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update a/aliased +--EXPECT-LOCK-- +{ + "packages": [ + { + "name": "a/aliased", "version": "3.0.2", + "type": "library" + }, + { + "name": "a/aliased2", "version": "3.0.2", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [{ + "package": "a/aliased", + "version": "3.0.2.0", + "alias": "3.0.3", + "alias_normalized": "3.0.3.0" + }], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT-- +Installing a/aliased (3.0.2) +Marking a/aliased (3.0.3) as installed, alias of a/aliased (3.0.2) +Installing a/aliased2 (3.0.2) diff --git a/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test b/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test index 28229eb87..1500d156b 100644 --- a/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test +++ b/tests/Composer/Test/Fixtures/installer/aliases-with-require-dev.test @@ -61,12 +61,12 @@ update } ], "aliases": [{ - "package": "a/aliased2", + "package": "a/aliased", "version": "dev-next", "alias": "4.1.0-RC2", "alias_normalized": "4.1.0.0-RC2" }, { - "package": "a/aliased", + "package": "a/aliased2", "version": "dev-next", "alias": "4.1.0-RC2", "alias_normalized": "4.1.0.0-RC2" diff --git a/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test b/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test new file mode 100644 index 000000000..172288f75 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/root-alias-gets-loaded-for-locked-pkgs.test @@ -0,0 +1,54 @@ +--TEST-- +Newly defined root alias does not get loaded if package is loaded from lock file +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "some/dep", "version": "dev-main" }, + { "name": "foo/pkg", "version": "1.0.0", "require": {"some/dep": "^1"} } + ] + } + ], + "require": { + "some/dep": "dev-main as 1.0.0", + "foo/pkg": "^1.0" + } +} +--LOCK-- +{ + "packages": [ + { "name": "some/dep", "version": "dev-main" } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--INSTALLED-- +[ + { "name": "some/dep", "version": "dev-main" } +] +--RUN-- +update foo/pkg + +--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 foo/pkg ^1.0 -> satisfiable by foo/pkg[1.0.0]. + - foo/pkg 1.0.0 requires some/dep ^1 -> found some/dep[dev-main] but it does not match the constraint. + +Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. + +--EXPECT--