Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5a0b594
2313917-113.patch no yml
tedbow Jul 30, 2019
deaa8ea
rename to 99x module
tedbow Jul 30, 2019
2d31e90
add test coverage for themes
tedbow Jul 30, 2019
672f566
Add update_test_semver_update_n
tedbow Jul 30, 2019
ec9b05e
fix assertThemeIncompatibleText
tedbow Jul 30, 2019
472fc91
128
tedbow Jul 30, 2019
f2ec8ac
Revert "128"
tedbow Jul 30, 2019
ba97bd2
Revert "Revert "128""
tedbow Jul 30, 2019
71eecc9
add check to installer
tedbow Jul 31, 2019
c7dbacc
update installer
tedbow Jul 31, 2019
f2147f0
check if module exists
tedbow Aug 2, 2019
7d0a3e3
interdiff 136
tedbow Aug 8, 2019
de778fd
add core_depedency optional key
tedbow Aug 8, 2019
faa3803
fix InfoParserUnitTest.php
tedbow Aug 8, 2019
f383b5b
undo core/modules/field_ui/tests/src/Functional/ManageDisplayTest.php
tedbow Aug 8, 2019
2994ba5
strict parsing wip
tedbow Aug 9, 2019
c123d23
add test for extra validation
tedbow Aug 9, 2019
a06b671
use 8.7.7 var
tedbow Aug 9, 2019
19336ab
cache evaluated constraints
tedbow Aug 10, 2019
50fa039
protected
tedbow Aug 10, 2019
642ef7b
parser test clean up
tedbow Aug 14, 2019
fa5efb4
more test clean up parser
tedbow Aug 14, 2019
b389fa1
180
tedbow Aug 15, 2019
86f17f5
simplify
tedbow Aug 15, 2019
a4336e4
move core_incompatible out of parser
tedbow Aug 16, 2019
15a3e3d
fix tests for parser
tedbow Aug 16, 2019
7d55e34
delete DrupalSemver
tedbow Aug 19, 2019
65a4811
Remove check for core_dependency in update
tedbow Aug 19, 2019
95084dd
nit
tedbow Aug 19, 2019
d183dae
check core in module form
tedbow Aug 19, 2019
5b6d837
test prerelease errors
tedbow Aug 19, 2019
ce9299f
wim 193
tedbow Aug 19, 2019
534fdef
wip fix
tedbow Aug 20, 2019
d0cb4b1
tests hardening
tedbow Aug 20, 2019
49a76d7
remove chagnes in system_test_system_info_alter
tedbow Aug 20, 2019
9243bf3
wim 198
tedbow Aug 20, 2019
c6fc854
parser fix and other test changes
tedbow Aug 20, 2019
fc438f5
undo extra
tedbow Aug 20, 2019
c313672
move into catch
tedbow Aug 20, 2019
86d2f81
make all tests call 2x
tedbow Aug 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/includes/update.inc
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ function update_check_incompatibility($name, $type = 'module') {
$file = $themes[$name];
}
if (!isset($file)
|| !isset($file->info['core'])
|| $file->info['core'] != \Drupal::CORE_COMPATIBILITY
|| $file->info['core_incompatible']
|| version_compare(phpversion(), $file->info['php']) < 0) {
return TRUE;
}
Expand Down
106 changes: 105 additions & 1 deletion core/lib/Drupal/Core/Extension/InfoParserDynamic.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Drupal\Core\Extension;

use Composer\Semver\Semver;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Serialization\Yaml;

Expand All @@ -10,6 +11,34 @@
*/
class InfoParserDynamic implements InfoParserInterface {

/**
* The earliest Drupal version that supports the 'core_version_requirement'.
*/
const FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION = '8.7.7';

/**
* Determines if a version satisfies the given constraints.
*
* This method uses \Composer\Semver\Semver::satisfies() but returns FALSE if
* the version or constraints are not valid instead of throwing an exception.
*
* @param string $version
* The version.
* @param string $constraints
* The constraints.
*
* @return bool
* TRUE if the version satisfies the constraints, otherwise FALSE.
*/
protected static function satisfies(string $version, $constraints) {
try {
return Semver::satisfies($version, $constraints);
}
catch (\UnexpectedValueException $exception) {
return FALSE;
}
}

/**
* {@inheritdoc}
*/
Expand All @@ -28,6 +57,39 @@ public function parse($filename) {
if (!empty($missing_keys)) {
throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
}
if (!isset($parsed_info['core']) && !isset($parsed_info['core_version_requirement'])) {
throw new InfoParserException("The 'core' or the 'core_version_requirement' key must be present in " . $filename);
}
if (isset($parsed_info['core']) && !preg_match("/^\d\.x$/", $parsed_info['core'])) {
throw new InfoParserException("Invalid 'core' value \"{$parsed_info['core']}\" in " . $filename);
}
if (isset($parsed_info['core_version_requirement'])) {
$supports_pre_core_version_requirement_version = static::isConstraintSatisfiedByPreCoreVersionRequirementCoreVersion($parsed_info['core_version_requirement']);
// If the 'core_version_requirement' constraint does not satisfy any
// Drupal 8 versions before 8.7.7 then 'core' cannot be set or it will
// effectively support all versions of Drupal 8 because
// 'core_version_requirement' will be ignored in previous versions.
if (!$supports_pre_core_version_requirement_version && isset($parsed_info['core'])) {
throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) requires the 'core' not be set in " . $filename);
}
// 'core_version_requirement' can not be used to specify Drupal 8
// versions before 8.7.7 because these versions do not use the
// 'core_version_requirement' key. Do not throw the exception if the
// constraint also is satisfied by 8.0.0-alpha1 to allow constraints
// such as '^8' or '^8 || ^9'.
if ($supports_pre_core_version_requirement_version && !static::satisfies('8.0.0-alpha1', $parsed_info['core_version_requirement'])) {
throw new InfoParserException("The 'core_version_requirement' can not be used to specify compatibility specific version before " . static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION . " in $filename");
}
}

// Determine if the extension is compatible with the current version of
// Drupal core.
try {
$parsed_info['core_incompatible'] = !static::satisfies(\Drupal::VERSION, $parsed_info['core_version_requirement'] ?? $parsed_info['core']);
}
catch (\UnexpectedValueException $exception) {
$parsed_info['core_incompatible'] = TRUE;
}
if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
$parsed_info['version'] = \Drupal::VERSION;
}
Expand Down Expand Up @@ -60,7 +122,49 @@ public function parse($filename) {
* An array of required keys.
*/
protected function getRequiredKeys() {
return ['type', 'core', 'name'];
return ['type', 'name'];
}

/**
* Determines if a constraint is satisfied by earlier versions of Drupal.
*
* @param string $constraint
* A core semantic version constraint.
*
* @return bool
* TRUE if the constraint is satisfied by a core version that does not
* support the 'core_version_requirement' key in info.yml files.
*/
protected static function isConstraintSatisfiedByPreCoreVersionRequirementCoreVersion($constraint) {
static $evaluated_constraints = [];
if (!isset($evaluated_constraints[$constraint])) {
foreach (range(0, 7) as $minor) {
foreach (range(0, 20) as $patch) {
$minor_version = "8.$minor.$patch";
if ($minor_version === static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION) {
$evaluated_constraints[$constraint] = FALSE;
return $evaluated_constraints[$constraint];
}
if (static::satisfies($minor_version, $constraint)) {
$evaluated_constraints[$constraint] = TRUE;
return $evaluated_constraints[$constraint];
}
if ($patch === 0) {
foreach (['alpha', 'beta', 'rc'] as $suffix) {
foreach (range(0, 10) as $suffix_num) {
$pre_release_version = "$minor_version-$suffix$suffix_num";
if (static::satisfies($pre_release_version, $constraint)) {
$evaluated_constraints[$constraint] = TRUE;
return $evaluated_constraints[$constraint];
}
}
}
}
}
}
$evaluated_constraints[$constraint] = FALSE;
}
return $evaluated_constraints[$constraint];
}

}
17 changes: 13 additions & 4 deletions core/lib/Drupal/Core/Extension/ModuleInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,17 @@ public function addUninstallValidator(ModuleUninstallValidatorInterface $uninsta
*/
public function install(array $module_list, $enable_dependencies = TRUE) {
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
// Get all module data so we can find dependencies and sort and find the
// core requirements. The module list needs to be reset so that it can
// re-scan and include any new modules that may have been added directly
// into the filesystem.
$module_data = \Drupal::service('extension.list.module')->reset()->getList();
foreach ($module_list as $module) {
if (!empty($module_data[$module]->info['core_incompatible'])) {
throw new MissingDependencyException("Unable to install modules: module '$module' is incompatible with this version of Drupal core.");
}
}
if ($enable_dependencies) {
// Get all module data so we can find dependencies and sort.
// The module list needs to be reset so that it can re-scan and include
// any new modules that may have been added directly into the filesystem.
$module_data = \Drupal::service('extension.list.module')->reset()->getList();
$module_list = $module_list ? array_combine($module_list, $module_list) : [];
if ($missing_modules = array_diff_key($module_list, $module_data)) {
// One or more of the given modules doesn't exist.
Expand All @@ -110,6 +116,9 @@ public function install(array $module_list, $enable_dependencies = TRUE) {

// Skip already installed modules.
if (!isset($module_list[$dependency]) && !isset($installed_modules[$dependency])) {
if ($module_data[$dependency]->info['core_incompatible']) {
throw new MissingDependencyException("Unable to install modules: module '$module'. Its dependency module '$dependency' is incompatible with this version of Drupal core.");
}
$module_list[$dependency] = $dependency;
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/modules/system/src/Controller/SystemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public function themesPage() {

if (empty($theme->status)) {
// Ensure this theme is compatible with this version of core.
$theme->incompatible_core = !isset($theme->info['core']) || ($theme->info['core'] != \DRUPAL::CORE_COMPATIBILITY);
$theme->incompatible_core = $theme->info['core_incompatible'];
// Require the 'content' region to make sure the main page
// content has a common place in all themes.
$theme->incompatible_region = !isset($theme->info['regions']['content']);
Expand Down
10 changes: 7 additions & 3 deletions core/modules/system/src/Form/ModulesListForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,14 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
$reasons = [];

// Check the core compatibility.
if ($module->info['core'] != \Drupal::CORE_COMPATIBILITY) {
if ($module->info['core_incompatible']) {
$compatible = FALSE;
$reasons[] = $this->t('This version is not compatible with Drupal @core_version and should be replaced.', [
'@core_version' => \Drupal::CORE_COMPATIBILITY,
'@core_version' => \Drupal::VERSION,
]);
$row['#requires']['core'] = $this->t('Drupal Core (@core_requirement) (<span class="admin-missing">incompatible with</span> version @core_version)', [
'@core_requirement' => $module->info['core_version_requirement'] ?? $module->info['core'],
'@core_version' => \Drupal::VERSION,
]);
}

Expand Down Expand Up @@ -341,7 +345,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
}
// Disable the checkbox if the dependency is incompatible with this
// version of Drupal core.
elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) {
elseif ($modules[$dependency]->info['core_incompatible']) {
$row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', [
'@module' => $name,
]);
Expand Down
2 changes: 1 addition & 1 deletion core/modules/system/system.admin.inc
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ function template_preprocess_system_themes_page(&$variables) {
// Make sure to provide feedback on compatibility.
$current_theme['incompatible'] = '';
if (!empty($theme->incompatible_core)) {
$current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains the correct 'core' value.", ['@core_version' => \Drupal::CORE_COMPATIBILITY]);
$current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains a compatible 'core' or 'core_version_requirement' value.", ['@core_version' => \Drupal::VERSION]);
}
elseif (!empty($theme->incompatible_region)) {
$current_theme['incompatible'] = t("This theme is missing a 'content' region.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* @file
* Database to mimic the installation of the update_test_semver_update_n module.
*/

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

// Set the schema version.
$connection->merge('key_value')
->condition('collection', 'system.schema')
->condition('name', 'update_test_semver_update_n')
->fields([
'collection' => 'system.schema',
'name' => 'update_test_semver_update_n',
'value' => 'i:8000;',
])
->execute();

// Update core.extension.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['update_test_semver_update_n'] = 8000;
$connection->update('config')
->fields([
'data' => serialize($extensions),
])
->condition('collection', '')
->condition('name', 'core.extension')
->execute();
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: 'System core incompatible semver test'
type: module
description: 'Support module for testing core incompatible semver.'
package: Testing
version: 1.0.0
core_version_requirement: ^7
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: 'System core ^8 version test'
type: module
description: 'Support module for testing core using semver.'
package: Testing
version: 1.0.0
core_version_requirement: ^8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: 'System incompatible core 1.x version test'
type: module
description: 'Support module for testing system core incompatibility.'
package: Testing
version: 1.0.0
core: 1.x
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function system_test_system_info_alter(&$info, Extension $file, $type) {
'system_incompatible_core_version_dependencies_test',
'system_incompatible_module_version_test',
'system_incompatible_core_version_test',
'system_incompatible_core_version_test_1x',
])) {
$info['hidden'] = FALSE;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: 'Update test hook_update_n semver'
type: module
description: 'Support module for update testing with core semver value.'
package: Testing
version: VERSION
core_version_requirement: ^8
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

/**
* @file
* Update hooks for the update_test_semver_update_n module.
*/

/**
* Update 8001.
*/
function update_test_semver_update_n_update_8001() {
\Drupal::state()->set('update_test_semver_update_n_update_8001', 'Yes, I was run. Thanks for testing!');
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function testModulesListFormWithInvalidInfoFile() {

// Confirm that the error message is shown.
$this->assertSession()
->pageTextContains('Modules could not be listed due to an error: Missing required keys (core) in ' . $path . '/broken.info.yml');
->pageTextContains("The 'core' or the 'core_version_requirement' key must be present in " . $path . '/broken.info.yml');

// Check that the module filter text box is available.
$this->assertTrue($this->xpath('//input[@name="text"]'));
Expand Down
23 changes: 23 additions & 0 deletions core/modules/system/tests/src/Functional/Module/DependencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ public function testIncompatiblePhpVersionDependency() {
$this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
}

/**
* Tests enabling modules with different core version specifications.
*/
public function testCoreCompatibility() {
$assert_session = $this->assertSession();

// Test incompatible 'core_version_requirement'.
$this->drupalGet('admin/modules');
$assert_session->fieldDisabled('modules[system_incompatible_core_version_test_1x][enable]');
$assert_session->fieldDisabled('modules[system_core_incompatible_semver_test][enable]');

// Test compatible 'core_version_requirement' and compatible 'core'.
$this->drupalGet('admin/modules');
$assert_session->fieldEnabled('modules[common_test][enable]');
$assert_session->fieldEnabled('modules[system_core_semver_test][enable]');

// Ensure the modules can actually be installed.
$edit['modules[common_test][enable]'] = 'common_test';
$edit['modules[system_core_semver_test][enable]'] = 'system_core_semver_test';
$this->drupalPostForm('admin/modules', $edit, t('Install'));
$this->assertModules(['common_test', 'system_core_semver_test'], TRUE);
}

/**
* Tests enabling a module that depends on a module which fails hook_requirements().
*/
Expand Down
Loading