From 4646e6fcadd26ed8ee32890229ce141d6ec5db59 Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Wed, 2 Feb 2022 22:43:41 +0000 Subject: [PATCH] Issue #3261847 by phenaproxima, tedbow: Add helpful methods to compute the difference between two ComposerUtility objects --- package_manager/src/ComposerUtility.php | 64 ++++++++++++------- .../packages_comparison/active/composer.json | 1 + .../active/vendor/composer/installed.json | 16 +++++ .../packages_comparison/stage/composer.json | 1 + .../stage/vendor/composer/installed.json | 16 +++++ .../tests/src/Kernel/ComposerUtilityTest.php | 19 ++++++ src/Validator/StagedProjectsValidator.php | 47 ++++++++------ .../StagedProjectsValidatorTest.php | 4 +- 8 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 package_manager/tests/fixtures/packages_comparison/active/composer.json create mode 100644 package_manager/tests/fixtures/packages_comparison/active/vendor/composer/installed.json create mode 100644 package_manager/tests/fixtures/packages_comparison/stage/composer.json create mode 100644 package_manager/tests/fixtures/packages_comparison/stage/vendor/composer/installed.json diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php index a518bcfee8..32507915c1 100644 --- a/package_manager/src/ComposerUtility.php +++ b/package_manager/src/ComposerUtility.php @@ -6,6 +6,7 @@ use Composer\Composer; use Composer\Factory; use Composer\IO\NullIO; use Composer\Package\PackageInterface; +use Composer\Semver\Comparator; use Drupal\Component\Serialization\Json; /** @@ -133,35 +134,13 @@ class ComposerUtility { return array_values($core_packages); } - /** - * Returns all Drupal extension packages in the lock file. - * - * The following package types are considered Drupal extension packages: - * drupal-module, drupal-theme, drupal-custom-module, and drupal-custom-theme. - * - * @return \Composer\Package\PackageInterface[] - * All Drupal extension packages in the lock file, keyed by name. - */ - public function getDrupalExtensionPackages(): array { - $filter = function (PackageInterface $package): bool { - $drupal_package_types = [ - 'drupal-module', - 'drupal-theme', - 'drupal-custom-module', - 'drupal-custom-theme', - ]; - return in_array($package->getType(), $drupal_package_types, TRUE); - }; - return array_filter($this->getInstalledPackages(), $filter); - } - /** * Returns information on all installed packages. * * @return \Composer\Package\PackageInterface[] * All installed packages, keyed by name. */ - protected function getInstalledPackages(): array { + public function getInstalledPackages(): array { $installed_packages = $this->getComposer() ->getRepositoryManager() ->getLocalRepository() @@ -175,4 +154,43 @@ class ComposerUtility { return $packages; } + /** + * Returns the packages that are in the current project, but not in another. + * + * @param self $other + * A Composer utility wrapper around a different directory. + * + * @return \Composer\Package\PackageInterface[] + * The packages which are installed in the current project, but not in the + * other one, keyed by name. + */ + public function getPackagesNotIn(self $other): array { + return array_diff_key($this->getInstalledPackages(), $other->getInstalledPackages()); + } + + /** + * Returns the packages which have a different version in another project. + * + * This compares the current project with another one, and returns packages + * which are present in both, but in different versions. + * + * @param self $other + * A Composer utility wrapper around a different directory. + * + * @return \Composer\Package\PackageInterface[] + * The packages which are present in both the current project and the other + * one, but in different versions, keyed by name. + */ + public function getPackagesWithDifferentVersionsIn(self $other): array { + $theirs = $other->getInstalledPackages(); + + // Only compare packages that are both here and there. + $packages = array_intersect_key($this->getInstalledPackages(), $theirs); + + $filter = function (PackageInterface $package, string $name) use ($theirs): bool { + return Comparator::notEqualTo($package->getVersion(), $theirs[$name]->getVersion()); + }; + return array_filter($packages, $filter, ARRAY_FILTER_USE_BOTH); + } + } diff --git a/package_manager/tests/fixtures/packages_comparison/active/composer.json b/package_manager/tests/fixtures/packages_comparison/active/composer.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/package_manager/tests/fixtures/packages_comparison/active/composer.json @@ -0,0 +1 @@ +{} diff --git a/package_manager/tests/fixtures/packages_comparison/active/vendor/composer/installed.json b/package_manager/tests/fixtures/packages_comparison/active/vendor/composer/installed.json new file mode 100644 index 0000000000..2152ef82fb --- /dev/null +++ b/package_manager/tests/fixtures/packages_comparison/active/vendor/composer/installed.json @@ -0,0 +1,16 @@ +{ + "packages": [ + { + "name": "drupal/existing", + "version": "1.0.0" + }, + { + "name": "drupal/updated", + "version": "1.0.0" + }, + { + "name": "drupal/removed", + "version": "1.0.0" + } + ] +} diff --git a/package_manager/tests/fixtures/packages_comparison/stage/composer.json b/package_manager/tests/fixtures/packages_comparison/stage/composer.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/package_manager/tests/fixtures/packages_comparison/stage/composer.json @@ -0,0 +1 @@ +{} diff --git a/package_manager/tests/fixtures/packages_comparison/stage/vendor/composer/installed.json b/package_manager/tests/fixtures/packages_comparison/stage/vendor/composer/installed.json new file mode 100644 index 0000000000..562b5f5601 --- /dev/null +++ b/package_manager/tests/fixtures/packages_comparison/stage/vendor/composer/installed.json @@ -0,0 +1,16 @@ +{ + "packages": [ + { + "name": "drupal/existing", + "version": "1.0.0" + }, + { + "name": "drupal/updated", + "version": "1.1.0" + }, + { + "name": "drupal/added", + "version": "1.0.0" + } + ] +} diff --git a/package_manager/tests/src/Kernel/ComposerUtilityTest.php b/package_manager/tests/src/Kernel/ComposerUtilityTest.php index 2356c46209..3ac38a4870 100644 --- a/package_manager/tests/src/Kernel/ComposerUtilityTest.php +++ b/package_manager/tests/src/Kernel/ComposerUtilityTest.php @@ -72,4 +72,23 @@ class ComposerUtilityTest extends KernelTestBase { $this->assertSame($expected_packages, $packages); } + /** + * @covers ::getPackagesNotIn + * @covers ::getPackagesWithDifferentVersionsIn + */ + public function testPackageComparison(): void { + $fixture_dir = __DIR__ . '/../../fixtures/packages_comparison'; + $active = ComposerUtility::createForDirectory($fixture_dir . '/active'); + $staged = ComposerUtility::createForDirectory($fixture_dir . '/stage'); + + $added = $staged->getPackagesNotIn($active); + $this->assertSame(['drupal/added'], array_keys($added)); + + $removed = $active->getPackagesNotIn($staged); + $this->assertSame(['drupal/removed'], array_keys($removed)); + + $updated = $active->getPackagesWithDifferentVersionsIn($staged); + $this->assertSame(['drupal/updated'], array_keys($updated)); + } + } diff --git a/src/Validator/StagedProjectsValidator.php b/src/Validator/StagedProjectsValidator.php index c3ad1e878b..eec9e8f9fb 100644 --- a/src/Validator/StagedProjectsValidator.php +++ b/src/Validator/StagedProjectsValidator.php @@ -2,6 +2,7 @@ namespace Drupal\automatic_updates\Validator; +use Composer\Package\PackageInterface; use Drupal\automatic_updates\Updater; use Drupal\package_manager\Event\PreApplyEvent; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -39,8 +40,8 @@ final class StagedProjectsValidator implements EventSubscriberInterface { } try { - $active_packages = $stage->getActiveComposer()->getDrupalExtensionPackages(); - $staged_packages = $stage->getStageComposer()->getDrupalExtensionPackages(); + $active = $stage->getActiveComposer(); + $stage = $stage->getStageComposer(); } catch (\Throwable $e) { $event->addError([ @@ -55,8 +56,15 @@ final class StagedProjectsValidator implements EventSubscriberInterface { 'drupal-theme' => $this->t('theme'), 'drupal-custom-theme' => $this->t('custom theme'), ]; + $filter = function (PackageInterface $package) use ($type_map): bool { + return array_key_exists($package->getType(), $type_map); + }; + $new_packages = $stage->getPackagesNotIn($active); + $removed_packages = $active->getPackagesNotIn($stage); + $updated_packages = $active->getPackagesWithDifferentVersionsIn($stage); + // Check if any new Drupal projects were installed. - if ($new_packages = array_diff_key($staged_packages, $active_packages)) { + if ($new_packages = array_filter($new_packages, $filter)) { $new_packages_messages = []; foreach ($new_packages as $new_package) { @@ -77,7 +85,7 @@ final class StagedProjectsValidator implements EventSubscriberInterface { } // Check if any Drupal projects were removed. - if ($removed_packages = array_diff_key($active_packages, $staged_packages)) { + if ($removed_packages = array_filter($removed_packages, $filter)) { $removed_packages_messages = []; foreach ($removed_packages as $removed_package) { $removed_packages_messages[] = $this->t( @@ -96,22 +104,21 @@ final class StagedProjectsValidator implements EventSubscriberInterface { $event->addError($removed_packages_messages, $removed_packages_summary); } - // Get all the packages that are neither newly installed or removed to - // check if their version numbers changed. - if ($pre_existing_packages = array_diff_key($staged_packages, $removed_packages, $new_packages)) { - foreach ($pre_existing_packages as $package_name => $staged_existing_package) { - $active_package = $active_packages[$package_name]; - if ($staged_existing_package->getVersion() !== $active_package->getVersion()) { - $version_change_messages[] = $this->t( - "@type '@name' from @active_version to @staged_version.", - [ - '@type' => $type_map[$active_package->getType()], - '@name' => $active_package->getName(), - '@staged_version' => $staged_existing_package->getPrettyVersion(), - '@active_version' => $active_package->getPrettyVersion(), - ] - ); - } + // Check if any Drupal projects were neither installed or removed, but had + // their version numbers changed. + if ($updated_packages = array_filter($updated_packages, $filter)) { + $staged_packages = $stage->getInstalledPackages(); + + foreach ($updated_packages as $name => $updated_package) { + $version_change_messages[] = $this->t( + "@type '@name' from @active_version to @staged_version.", + [ + '@type' => $type_map[$updated_package->getType()], + '@name' => $updated_package->getName(), + '@staged_version' => $staged_packages[$name]->getPrettyVersion(), + '@active_version' => $updated_package->getPrettyVersion(), + ] + ); } if (!empty($version_change_messages)) { $version_change_summary = $this->formatPlural( diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php index 3799a239b9..1298f45791 100644 --- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php @@ -199,8 +199,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { "$fixtures_folder/version_changed", 'The update cannot proceed because the following Drupal projects were unexpectedly updated. Only Drupal Core updates are currently supported.', [ - "module 'drupal/test_module' from 1.3.0 to 1.3.1.", - "module 'drupal/dev-test_module' from 1.3.0 to 1.3.1.", + "module 'drupal/test_module' from 1.3.0 to 1.3.1.", + "module 'drupal/dev-test_module' from 1.3.0 to 1.3.1.", ], ], ]; -- GitLab