diff --git a/core/includes/install.inc b/core/includes/install.inc index 60e665516d..6e39ca5e40 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -11,6 +11,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\Extension\InfoParserDynamic; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -1136,8 +1137,8 @@ function install_profile_info($profile, $langcode = 'en') { $dependency_name_function = function ($dependency) { return Dependency::createFromString($dependency)->getName(); }; - if (!empty($info['composer_dependencies'])) { - $info['dependencies'] = array_keys($info['composer_dependencies']); + if (!empty($info[InfoParserDynamic::COMPOSER_DEPENDENCIES])) { + $info['dependencies'] = array_keys($info[InfoParserDynamic::COMPOSER_DEPENDENCIES]); } else { // Convert dependencies in [project:module] format. diff --git a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php index 44fd4d6b04..b51293feee 100644 --- a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php +++ b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php @@ -11,10 +11,17 @@ */ class InfoParserDynamic implements InfoParserInterface { + /** + * The earliest Drupal version that supports the 'core_version_requirement'. + */ + const FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION = '8.7.7'; + /** * The earliest Drupal version that supports the 'core_version_requirement'. */ - const FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION = '8.7.7'; + const FIRST_COMPOSER_JSON_SUPPORTED_VERSION = '8.8.0'; + + const COMPOSER_DEPENDENCIES = 'composer_dependencies'; /** * Determines if a version satisfies the given constraints. @@ -67,6 +74,9 @@ public function parse($filename) { throw new InfoParserException("If the 'core' or 'core_version_requirement' key is not provided a composer.json file is required in $filename"); } $parsed_info += $this->parseComposerFile($composer_filename); + if (static::isConstraintSatisfiedByPreviousVersion($parsed_info['core_version_requirement'], static::FIRST_COMPOSER_JSON_SUPPORTED_VERSION)) { + throw new InfoParserException('Core versions before ' . static::FIRST_COMPOSER_JSON_SUPPORTED_VERSION . "must be specified in the module info.yml in $filename"); + } } else { if (isset($parsed_info['core']) && !preg_match("/^\d\.x$/", $parsed_info['core'])) { @@ -75,7 +85,7 @@ public function parse($filename) { } if (isset($parsed_info['core_version_requirement'])) { - $supports_pre_core_version_requirement_version = static::isConstraintSatisfiedByPreCoreVersionRequirementCoreVersion($parsed_info['core_version_requirement']); + $supports_pre_core_version_requirement_version = static::isConstraintSatisfiedByPreviousVersion($parsed_info['core_version_requirement'], static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION); // 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 @@ -147,11 +157,11 @@ protected function getRequiredKeys() { * 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) { + protected static function isConstraintSatisfiedByPreviousVersion($constraint, $version) { static $evaluated_constraints = []; if (!isset($evaluated_constraints[$constraint])) { $evaluated_constraints[$constraint] = FALSE; - foreach (static::getAllPreCoreVersionRequirementCoreVersions() as $version) { + foreach (static::getAllPreviousCoreVersions($version) as $version) { if (static::satisfies($version, $constraint)) { $evaluated_constraints[$constraint] = TRUE; // The constraint only has to satisfy one version so break when the @@ -164,39 +174,43 @@ protected static function isConstraintSatisfiedByPreCoreVersionRequirementCoreVe } /** - * Gets all the version of Drupal 8 before 8.7.7. + * Gets all the version of Drupal 8 before a specific version. + * + * + * @param string $version + * The version to get versions before. * * @return array * All of the applicable Drupal 8 releases. */ - protected static function getAllPreCoreVersionRequirementCoreVersions() { - static $versions = []; - if (empty($versions)) { + protected static function getAllPreviousCoreVersions($version) { + static $versions_lists = []; + if (empty($versions_lists[$version])) { // Loop through all minor versions including 8.7. foreach (range(0, 7) as $minor) { // The largest patch number in a release was 17 in 8.6.17. Use 27 to // leave room for future security releases. foreach (range(0, 27) as $patch) { $patch_version = "8.$minor.$patch"; - if ($patch_version === static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION) { + if ($patch_version === $version) { // Reverse the order of the versions so that they will be evaluated // from the most recent versions first. - $versions = array_reverse($versions); - return $versions; + $versions_lists[$version] = array_reverse($versions_lists[$version]); + return $versions_lists[$version]; } - $versions[] = $patch_version; + $versions_lists[$version][] = $patch_version; if ($patch === 0) { foreach (['alpha', 'beta', 'rc'] as $prerelease) { // The largest prerelease number was in 8.0.0-beta16. foreach (range(0, 16) as $prerelease_number) { - $versions[] = "$patch_version-$prerelease$prerelease_number"; + $versions_lists[$version][] = "$patch_version-$prerelease$prerelease_number"; } } } } } } - return $versions; + return $versions_lists[$version]; } /** @@ -206,7 +220,7 @@ protected static function getAllPreCoreVersionRequirementCoreVersions() { * The file name. * * @return array - * Parsed info file. + * Parsed composer.json file. */ protected function parseComposerFile($filename) { if (!$parsed_info = json_decode(file_get_contents($filename), TRUE)) { @@ -223,7 +237,7 @@ protected function parseComposerFile($filename) { $parsed_info['core_version_requirement'] = $constraint; continue; } - $parsed_info['composer_dependencies'][$name] = $constraint; + $parsed_info[static::COMPOSER_DEPENDENCIES][$name] = $constraint; } if (empty($parsed_info['core_version_requirement'])) { throw new InfoParserException("The require key must at least specify a 'drupal/drupal' version in $filename"); diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php index 39688ee81f..c8dd281e35 100644 --- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php +++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php @@ -220,8 +220,8 @@ protected function getInstalledExtensionNames() { protected function ensureRequiredDependencies(Extension $module, array $modules = []) { if (!empty($module->info['required'])) { // Modern composer.json file. - if (!empty($module->info['composer_dependencies'])) { - $dependencies = array_keys($module->info['composer_dependencies']); + if (!empty($module->info[InfoParserDynamic::COMPOSER_DEPENDENCIES])) { + $dependencies = array_keys($module->info[InfoParserDynamic::COMPOSER_DEPENDENCIES]); } else { // Legacy .info.yml files. diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 1382f2a64d..0497150d81 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -224,8 +224,8 @@ protected function add($type, $name, $path) { public function buildModuleDependencies(array $modules) { foreach ($modules as $module) { $graph[$module->getName()]['edges'] = []; - if (isset($module->info['composer_dependencies']) && is_array($module->info['composer_dependencies'])) { - foreach ($module->info['composer_dependencies'] as $name => $constraint) { + if (isset($module->info[InfoParserDynamic::COMPOSER_DEPENDENCIES]) && is_array($module->info[InfoParserDynamic::COMPOSER_DEPENDENCIES])) { + foreach ($module->info[InfoParserDynamic::COMPOSER_DEPENDENCIES] as $name => $constraint) { $graph[$module->getName()]['edges'][$name] = new Composer($constraint, $name); } } diff --git a/core/modules/system/tests/modules/composer_dependencies_test/composer.json b/core/modules/system/tests/modules/composer_dependencies_test/composer.json index 3efd5810f4..45606fd25f 100644 --- a/core/modules/system/tests/modules/composer_dependencies_test/composer.json +++ b/core/modules/system/tests/modules/composer_dependencies_test/composer.json @@ -2,7 +2,7 @@ "name": "drupal/composer_dependencies_test", "type": "drupal-module", "require": { - "drupal/drupal": "^8", + "drupal/drupal": "^8.8", "drupal/common_test": "^2.4.2" } } diff --git a/core/modules/system/tests/modules/system_composer_dependencies_test/composer.json b/core/modules/system/tests/modules/system_composer_dependencies_test/composer.json index c5fa5416f2..a3a400605a 100644 --- a/core/modules/system/tests/modules/system_composer_dependencies_test/composer.json +++ b/core/modules/system/tests/modules/system_composer_dependencies_test/composer.json @@ -3,7 +3,7 @@ "description": "Support module for testing system composer dependencies.", "type": "drupal-module", "require": { - "drupal/drupal": "^8", + "drupal/drupal": "^8.8", "drupal/missing_composer_dependency": "^1" } } diff --git a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php index ae322d8fa5..2a774c1167 100644 --- a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php @@ -462,7 +462,59 @@ public function testComposerJson() { "type": "drupal-module", "license": "GPL-2.0-or-later", "require": { - "drupal/drupal": "^8", + "drupal/drupal": "^8.8", + "drupal/field": "~8.0", + "smurfcore/log": "~1.0" +}, +"version": "VERSION" +} +COMMONTEST_JSON; + + vfsStream::setup('modules'); + foreach (['1', '2'] as $file_delta) { + $folder = "fixtures_$file_delta"; + vfsStream::create([ + $folder => [ + 'common_no_dependencies.info.yml' => $common_info, + 'composer.json' => $common_json, + ], + ]); + $info_values = $this->infoParser->parse(vfsStream::url("modules/$folder/common_no_dependencies.info.yml")); + $this->assertEquals('A simple string', $info_values['simple_string']); + $this->assertEquals(\Drupal::VERSION, $info_values['version']); + $this->assertEquals('Common test', $info_values['name']); + $this->assertFalse(isset($info_values['dependencies'])); + $this->assertSame(['field' => '~8.0'], $info_values[InfoParserDynamic::COMPOSER_DEPENDENCIES]); + $this->assertSame('^8.8', $info_values['core_version_requirement']); + $this->assertFalse(isset($info_values['core'])); + $this->assertEquals('module', $info_values['type']); + } + + } + + /** + * Tests a composer.json file with invalid 'drupal/drupal' value. + * + * @covers ::parse + */ + public function testComposerInvalidCoreJson() { + $common_info = << [ + 'fixtures1' => [ + 'common_no_dependencies.info.yml' => $common_info, + 'composer.json' => $common_json, + ], + 'fixtures2' => [ 'common_no_dependencies.info.yml' => $common_info, 'composer.json' => $common_json, ], ]); - $info_values = $this->infoParser->parse(vfsStream::url('modules/fixtures/common_no_dependencies.info.yml')); - $this->assertEquals('A simple string', $info_values['simple_string']); - $this->assertEquals(\Drupal::VERSION, $info_values['version']); - $this->assertEquals('Common test', $info_values['name']); - $this->assertFalse(isset($info_values['dependencies'])); - $this->assertSame(['field' => '~8.0'], $info_values['composer_dependencies']); - $this->assertSame('^8', $info_values['core_version_requirement']); - $this->assertFalse(isset($info_values['core'])); - $this->assertEquals('module', $info_values['type']); - } + // Set the expected exception for the 2nd call to parse(). + $this->expectException(InfoParserException::class); + $this->expectExceptionMessage('Core versions before 8.8.0must be specified in the module info.yml in vfs://modules/fixtures2/common_no_dependencies.info.yml'); + + try { + $this->infoParser->parse(vfsStream::url("modules/fixtures1/common_no_dependencies.info.yml")); + } + catch (InfoParserException $exception) { + $this->assertSame('Core versions before 8.8.0must be specified in the module info.yml in vfs://modules/fixtures1/common_no_dependencies.info.yml', $exception->getMessage()); + $this->infoParser->parse(vfsStream::url("modules/fixtures2/common_no_dependencies.info.yml")); + } + + } /** * Tests composer.json file with 'drupal/drupal' specified. * @@ -550,7 +609,6 @@ public function testInfoParserDependenciesNoCore() { 'dependencies_no_core.info.txt' => $missing_composer, ], ]); - $filename = vfsStream::url('modules/fixtures/dependencies_no_core.info.txt'); $this->expectException('\Drupal\Core\Extension\InfoParserException'); $this->expectExceptionMessage("If the 'dependencies' key is used the 'core' or 'core_version_requirement' key is required in vfs://modules/fixtures/dependencies_no_core.info.txt"); $this->infoParser->parse(vfsStream::url('modules/fixtures/dependencies_no_core.info.txt'));