Unverified Commit 1ab223a5 authored by Alex Pott's avatar Alex Pott
Browse files

task: #3561302 Register equivalent updates on site install

By: godotislate
By: xjm
By: catch
By: smustgrave
By: nicxvan
By: alexpott
By: quietone
parent cdefa18d
Loading
Loading
Loading
Loading
Loading
+13 −1
Changes for core/includes/update.inc: 13 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
use Drupal\Component\Graph\Graph;
use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent;
use Drupal\Core\Utility\Error;
use Drupal\Core\Update\EquivalentUpdate;

@@ -173,7 +174,9 @@ function update_do_one($module, $number, $dependency_map, &$context): void {
  }

  $ret = [];
  $equivalent_update = \Drupal::service('update.update_hook_registry')->getEquivalentUpdate($module, $number);
  /** @var \Drupal\Core\Update\UpdateHookRegistry $update_hook_registry */
  $update_hook_registry = \Drupal::service('update.update_hook_registry');
  $equivalent_update = $update_hook_registry->getEquivalentUpdate($module, $number);
  if ($equivalent_update instanceof EquivalentUpdate) {
    $ret['results']['query'] = $equivalent_update->toSkipMessage();
    $ret['results']['success'] = TRUE;
@@ -181,6 +184,15 @@ function update_do_one($module, $number, $dependency_map, &$context): void {
  }
  elseif (function_exists($function)) {
    try {
      // Check for the MarkFutureUpdateEquivalent attribute on the update
      // function to register a future equivalent update.
      $attributes = (new \ReflectionFunction($function))->getAttributes(MarkFutureUpdateEquivalent::class);
      foreach ($attributes as $attribute) {
        /** @var \Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent $instance */
        $instance = $attribute->newInstance();
        $update_hook_registry->markFutureUpdateEquivalent($instance->futureUpdateNumber, $instance->futureVersionString, $module, $number);
      }

      $ret['results']['query'] = $function($context['sandbox']);
      $ret['results']['success'] = TRUE;
    }
+18 −0
Changes for core/lib/Drupal/Core/Extension/ModuleInstaller.php: 18 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
use Drupal\Core\Extension\Exception\ObsoleteExtensionException;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent;
use Drupal\Core\Update\UpdateHookRegistry;
use Drupal\Core\Utility\Error;
use Psr\Log\LoggerInterface;
@@ -386,6 +387,23 @@ private function doInstall(array $module_list, array $installed_modules, bool $s
      $version = \Drupal::CORE_MINIMUM_SCHEMA_VERSION;
      $versions = $this->updateRegistry->getAvailableUpdates($module);
      if ($versions) {
        foreach ($versions as $schema_version) {
          // While update hooks do not run during module installation, check
          // for the MarkFutureUpdateEquivalent attribute on every update
          // function to make sure the equivalent update is registered.
          try {
            $attributes = (new \ReflectionFunction("{$module}_update_$schema_version"))
              ->getAttributes(MarkFutureUpdateEquivalent::class);
            foreach ($attributes as $attribute) {
              /** @var \Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent $instance */
              $instance = $attribute->newInstance();
              $this->updateRegistry->markFutureUpdateEquivalent($instance->futureUpdateNumber, $instance->futureVersionString, $module, $schema_version);
            }
          }
          catch (\Throwable $t) {
            Error::logException($this->logger, $t);
          }
        }
        $version = max(max($versions), $version);
      }

+4 −3
Changes for core/lib/Drupal/Core/Extension/module.api.php: 4 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -666,10 +666,11 @@ function hook_install_tasks_alter(&$tasks, $install_state) {
 * Future updates can be marked as equivalent by adding the following code to an
 * update.
 * @code
 * function my_module_update_10400() {
 *   \Drupal::service('update.update_hook_registry')->markFutureUpdateEquivalent(11101, '11.1.1');
 * use Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent;
 *
 *   // The rest of the update function.
 * #[MarkFutureUpdateEquivalent(11101, '11.1.1')]
 * function my_module_update_10400(): void {
 *   // The function can be no-op.
 * }
 * @endcode
 *
+44 −0
Changes for core/lib/Drupal/Core/Update/Attribute/MarkFutureUpdateEquivalent.php: 44 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Update\Attribute;

/**
 * Attribute used by hook_update_N() to register an equivalent update.
 *
 * Updates can be registered as equivalent when they are backported to a
 * previous, but still supported, major version. Update functions using this
 * attribute will register the update as equivalent to the given future update.
 * The future update is specified by future hook_update_N number and the future
 * module version. The update itself can be no-op.
 *
 * By itself, hook_update_N() hooks do not run on module install. However, the
 * presence of the attribute on a hook_update_N() will register the equivalent
 * update when the module is installed.
 *
 * Example:
 *
 * @code
 * use Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent;
 *
 * #[MarkFutureUpdateEquivalent(2005, '2.10')]
 * function MODULE_update_1040(): void {
 * }
 * @endcode
 * Here, database update MODULE_update_2005() is added to version 2.x of MODULE.
 * When that same update is backported to 1.x, it is given its own update
 * number, MODULE_update_1040(). This ensures that a site that has run this
 * MODULE_update_1040() does not also run MODULE_update_2005().
 *
 * @see update_api
 */
#[\Attribute(\Attribute::TARGET_FUNCTION)]
readonly class MarkFutureUpdateEquivalent {

  public function __construct(
    public int $futureUpdateNumber,
    public string $futureVersionString,
  ) {}

}
+24 −12
Changes for core/lib/Drupal/Core/Update/UpdateHookRegistry.php: 24 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -189,25 +189,37 @@ public function getAllInstalledVersions(): array {
  /**
   * Marks a future update as equivalent to the current update running.
   *
   * Updates can be marked as equivalent when they are backported to a
   * previous, but still supported, major version. For example:
   * - A 2.x hook_update_N() would be added as normal, for example:
   *   MODULE_update_2005().
   * - When that same update is backported to 1.x, it is given its own update
   *   number, for example: MODULE_update_1040(). In this update, a call to
   *   @code
   *   \Drupal::service('update.update_hook_registry')->markFutureUpdateEquivalent(2005, '2.10')
   *   @endcode
   *   is added to ensure that a site that has run this update does not run
   *   MODULE_update_2005().
   * To register equivalent updates use the attribute
   * \Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent.
   *
   * @param int $future_update_number
   *   The future update number.
   * @param string $future_version_string
   *   The version that contains the future update.
   * phpcs:ignore Drupal.Commenting.FunctionComment.ParamNameNoMatch
   * @param string $module
   *   The module containing the update. Will be determined from stack trace if
   *   not set.
   * phpcs:ignore Drupal.Commenting.FunctionComment.ExtraParamComment
   * @param int $ran_update_number
   *   The current update number. Will be determined from stack trace if not
   *   set.
   *
   * @todo Uncomment the new $module and $ran_update_number method parameters
   *   before drupal:13.0.0. https://www.drupal.org/node/3617733
   *
   * @internal
   *
   * @see \Drupal\Core\Update\Attribute\MarkFutureUpdateEquivalent
   */
  public function markFutureUpdateEquivalent(int $future_update_number, string $future_version_string): void {
  public function markFutureUpdateEquivalent(int $future_update_number, string $future_version_string /* , string $module, int $ran_update_number */): void {
    $arguments = func_get_args();
    $module = $arguments[2] ?? NULL;
    $ran_update_number = $arguments[3] ?? NULL;
    if (!$module || !$ran_update_number) {
      @trigger_error('Calling ' . __METHOD__ . '() without both the $module and $ran_update_number arguments is deprecated in drupal:11.5.0 and they will be required in drupal:13.0.0. See https://www.drupal.org/node/3572245', E_USER_DEPRECATED);
      [$module, $ran_update_number] = $this->determineModuleAndVersion();
    }

    if ($ran_update_number > $future_update_number) {
      throw new \LogicException(sprintf(
Loading