From 73730fb909fdf2cc033397d2a4d199ef88cff878 Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Wed, 19 Jan 2022 14:26:37 +0000 Subject: [PATCH] Issue #3258045 by phenaproxima: Ensure update command in Stage::require() only updates specified packages --- package_manager/src/ComposerUtility.php | 19 +------ package_manager/src/Stage.php | 42 +++++++++------ src/Updater.php | 16 +++--- tests/fixtures/fake-site/composer.lock | 7 ++- tests/src/Kernel/UpdaterTest.php | 70 ++++++++++++++++--------- 5 files changed, 83 insertions(+), 71 deletions(-) diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php index cfab9e4087..4cf3d48ddd 100644 --- a/package_manager/src/ComposerUtility.php +++ b/package_manager/src/ComposerUtility.php @@ -128,23 +128,6 @@ class ComposerUtility { return array_values($core_packages); } - /** - * Returns the names of the core packages in the dev dependencies. - * - * All packages listed in ../core_packages.json are considered core packages. - * - * @return string[] - * The names of the core packages in the dev requirements. - * - * @todo Make this return a keyed array of packages, not just names in - * https://www.drupal.org/i/3258059. - */ - public function getCoreDevPackageNames(): array { - $dev_packages = $this->composer->getPackage()->getDevRequires(); - $dev_packages = array_keys($dev_packages); - return array_intersect(static::getCorePackageList(), $dev_packages); - } - /** * Returns all Drupal extension packages in the lock file. * @@ -174,7 +157,7 @@ class ComposerUtility { * All packages in the lock file, keyed by name. */ protected function getLockedPackages(): array { - $locked_packages = $this->composer->getLocker() + $locked_packages = $this->getComposer()->getLocker() ->getLockedRepository(TRUE) ->getPackages(); diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php index c109ea85ed..b1b9483f6d 100644 --- a/package_manager/src/Stage.php +++ b/package_manager/src/Stage.php @@ -231,28 +231,38 @@ class Stage { } /** - * Requires packages in the staging area. - * - * @param string[] $constraints - * The packages to require, in the form 'vendor/name:version'. - * @param bool $dev - * (optional) Whether the packages should be required as dev dependencies. - * Defaults to FALSE. + * Adds or updates packages in the staging area. + * + * @param string[] $runtime + * The packages to add as regular top-level dependencies, in the form + * 'vendor/name:version'. + * @param string[] $dev + * (optional) The packages to add as dev dependencies, in the form + * 'vendor/name:version'. Defaults to an empty array. */ - public function require(array $constraints, bool $dev = FALSE): void { + public function require(array $runtime, array $dev = []): void { $this->checkOwnership(); - $command = array_merge(['require', '--no-update'], $constraints); + $this->dispatch(new PreRequireEvent($this)); + $dir = $this->getStageDirectory(); + + // Change the runtime and dev requirements as needed, but don't update + // the installed packages yet. + if ($runtime) { + $command = array_merge(['require', '--no-update'], $runtime); + $this->stager->stage($command, $dir); + } if ($dev) { - $command[] = '--dev'; + $command = array_merge(['require', '--dev', '--no-update'], $dev); + $this->stager->stage($command, $dir); + } + + // If constraints were changed, update those packages. + if ($runtime || $dev) { + $command = array_merge(['update', '--with-all-dependencies'], $runtime, $dev); + $this->stager->stage($command, $dir); } - $this->dispatch(new PreRequireEvent($this)); - $dir = $this->getStageDirectory(); - $this->stager->stage($command, $dir); - // @todo Limit the update command to only packages in $constraints in - // https://www.drupal.org/i/3257849. - $this->stager->stage(['update', '--with-all-dependencies'], $dir); $this->dispatch(new PostRequireEvent($this)); } diff --git a/src/Updater.php b/src/Updater.php index cbc1b3a02c..93aa600370 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -35,11 +35,12 @@ class Updater extends Stage { 'dev' => [], ]; + $require_dev = $composer->getComposer() + ->getPackage() + ->getDevRequires(); foreach ($composer->getCorePackageNames() as $package) { - $package_versions['production'][$package] = $project_versions['drupal']; - } - foreach ($composer->getCoreDevPackageNames() as $package) { - $package_versions['dev'][$package] = $project_versions['drupal']; + $group = array_key_exists($package, $require_dev) ? 'dev' : 'production'; + $package_versions[$group][$package] = $project_versions['drupal']; } // Ensure that package versions are available to pre-create event @@ -79,12 +80,7 @@ class Updater extends Stage { return $requirements; }; $versions = array_map($map, $this->getPackageVersions()); - - $this->require($versions['production']); - - if ($versions['dev']) { - $this->require($versions['dev'], TRUE); - } + $this->require($versions['production'], $versions['dev']); } /** diff --git a/tests/fixtures/fake-site/composer.lock b/tests/fixtures/fake-site/composer.lock index dd29a0514d..05715cda77 100644 --- a/tests/fixtures/fake-site/composer.lock +++ b/tests/fixtures/fake-site/composer.lock @@ -19,5 +19,10 @@ "version": "9.8.0" } ], - "packages-dev": [] + "packages-dev": [ + { + "name": "drupal/core-dev", + "version": "9.8.0" + } + ] } diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php index 7cabf9e29d..a1c96278ce 100644 --- a/tests/src/Kernel/UpdaterTest.php +++ b/tests/src/Kernel/UpdaterTest.php @@ -57,7 +57,7 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { $id = $this->container->get('automatic_updates.updater')->begin([ 'drupal' => '9.8.1', ]); - // Rebuild the container to ensure the project versions are kept in state. + // Rebuild the container to ensure the package versions are persisted. /** @var \Drupal\Core\DrupalKernel $kernel */ $kernel = $this->container->get('kernel'); $kernel->rebuildContainer(); @@ -66,37 +66,55 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { $this->container->set('package_manager.path_locator', $locator->reveal()); $this->setCurrentUser($user); + /** @var \Drupal\automatic_updates\Updater $updater */ + $updater = $this->container->get('automatic_updates.updater'); + + // Ensure that the target package versions are what we expect. + $expected_versions = [ + 'production' => [ + 'drupal/core-recommended' => '9.8.1', + ], + 'dev' => [ + 'drupal/core-dev' => '9.8.1', + ], + ]; + $this->assertSame($expected_versions, $updater->claim($id)->getPackageVersions()); + // When we call Updater::stage(), the stored project versions should be // read from state and passed to Composer Stager's Stager service, in the // form of a Composer command. This is done using package_manager_bypass's // invocation recorder, rather than a regular mock, in order to test that // the invocation recorder itself works. - // The production dependencies should be updated first... - $expected_require_arguments = [ - 'require', - '--no-update', - 'drupal/core-recommended:9.8.1', - ]; - // ...followed by the dev dependencies. - $expected_require_dev_arguments = [ - 'require', - '--no-update', - 'drupal/core-dev:9.8.1', - '--dev', + // The production requirements are changed first, followed by the dev + // requirements. Then the installed packages are updated. + $expected_arguments = [ + [ + 'require', + '--no-update', + 'drupal/core-recommended:9.8.1', + ], + [ + 'require', + '--dev', + '--no-update', + 'drupal/core-dev:9.8.1', + ], + [ + 'update', + '--with-all-dependencies', + 'drupal/core-recommended:9.8.1', + 'drupal/core-dev:9.8.1', + ], ]; - // In both cases, `composer update` will be called separately. - $expected_update_arguments = ['update', '--with-all-dependencies']; - - $this->container->get('automatic_updates.updater')->claim($id)->stage(); - - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ - $stager = $this->container->get('package_manager.stager'); - $invocation_arguments = $stager->getInvocationArguments(); - $this->assertCount(4, $invocation_arguments); - $this->assertSame($expected_require_arguments, $invocation_arguments[0][0]); - $this->assertSame($expected_update_arguments, $invocation_arguments[1][0]); - $this->assertSame($expected_require_dev_arguments, $invocation_arguments[2][0]); - $this->assertSame($expected_update_arguments, $invocation_arguments[3][0]); + $updater->stage(); + + $actual_arguments = $this->container->get('package_manager.stager') + ->getInvocationArguments(); + + $this->assertSame(count($expected_arguments), count($actual_arguments)); + foreach ($actual_arguments as $i => [$arguments]) { + $this->assertSame($expected_arguments[$i], $arguments); + } } /** -- GitLab