Issue #3272110 by Mile23, ravi.shankar, Spokje, xjm, andypost, joachim: Drupal 9 and 10's Drupal\Component composer.json files are totally out of date

merge-requests/2643/merge
catch 2022-08-21 16:36:57 +09:00
parent e91369d0b8
commit bbdf337d77
32 changed files with 1008 additions and 358 deletions

View File

@ -108,7 +108,9 @@
"@composer update phpunit/phpunit --with-dependencies --no-progress" "@composer update phpunit/phpunit --with-dependencies --no-progress"
], ],
"post-update-cmd": [ "post-update-cmd": [
"Drupal\\Composer\\Composer::generateMetapackages" "Drupal\\Composer\\Composer::generateMetapackages",
"Drupal\\Composer\\Composer::generateComponentPackages"
], ],
"phpcs": "phpcs --standard=core/phpcs.xml.dist --", "phpcs": "phpcs --standard=core/phpcs.xml.dist --",
"phpcbf": "phpcbf --standard=core/phpcs.xml.dist --" "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --"

View File

@ -6,6 +6,7 @@ use Composer\Composer as ComposerApp;
use Composer\Script\Event; use Composer\Script\Event;
use Composer\Semver\Comparator; use Composer\Semver\Comparator;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
use Drupal\Composer\Generator\ComponentGenerator;
use Drupal\Composer\Generator\PackageGenerator; use Drupal\Composer\Generator\PackageGenerator;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
@ -30,6 +31,17 @@ class Composer {
$generator->generate($event->getIO(), getcwd()); $generator->generate($event->getIO(), getcwd());
} }
/**
* Update component packages whenever composer.lock is updated.
*
* @param \Composer\Script\Event $event
* The Composer event.
*/
public static function generateComponentPackages(Event $event): void {
$generator = new ComponentGenerator();
$generator->generate($event, getcwd());
}
/** /**
* Set the version of Drupal; used in release process and by the test suite. * Set the version of Drupal; used in release process and by the test suite.
* *

View File

@ -0,0 +1,234 @@
<?php
namespace Drupal\Composer\Generator;
use Composer\IO\IOInterface;
use Composer\Semver\VersionParser;
use Composer\Script\Event;
use Composer\Util\Filesystem;
use Drupal\Composer\Composer;
use Drupal\Composer\Generator\Util\DrupalCoreComposer;
use Drupal\Composer\Util\SemanticVersion;
use Symfony\Component\Finder\Finder;
/**
* Reconciles Drupal component dependencies with core.
*/
class ComponentGenerator {
/**
* Relative path from Drupal root to the component directory.
*
* @var string
*/
protected static $relativeComponentPath = 'core/lib/Drupal/Component';
/**
* Full path to the component directory.
*
* @var string
*/
protected $componentBaseDir;
/**
* Data from drupal/drupal's composer.json file.
*
* @var \Drupal\Composer\Generator\Util\DrupalCoreComposer
*/
protected $drupalProjectInfo;
/**
* Data from drupal/core's composer.json file.
*
* @var \Drupal\Composer\Generator\Util\DrupalCoreComposer
*/
protected $drupalCoreInfo;
/**
* ComponentGenerator constructor.
*/
public function __construct() {
$this->componentBaseDir = dirname(__DIR__, 2) . '/' . static::$relativeComponentPath;
}
/**
* Find all the composer.json files for components.
*
* @return \Symfony\Component\Finder\Finder
* A Finder object with all the composer.json files for components.
*/
public function getComponentPathsFinder(): Finder {
$composer_json_finder = new Finder();
$composer_json_finder->name('composer.json')
->in($this->componentBaseDir)
->ignoreUnreadableDirs()
->depth(1);
return $composer_json_finder;
}
/**
* Reconcile Drupal's components whenever composer.lock is updated.
*
* @param \Composer\Script\Event $event
* The Composer event.
* @param string $base_dir
* Directory where drupal/drupal repository is located.
*/
public function generate(Event $event, string $base_dir): void {
$io = $event->getIO();
// General information from drupal/drupal and drupal/core composer.json
// and composer.lock files.
$this->drupalProjectInfo = DrupalCoreComposer::createFromPath($base_dir);
$this->drupalCoreInfo = DrupalCoreComposer::createFromPath($base_dir . '/core');
$changed = FALSE;
/** @var \Symfony\Component\Finder\SplFileInfo $component_composer_json */
foreach ($this->getComponentPathsFinder()->getIterator() as $component_composer_json) {
$changed |= $this->generateComponentPackage($event, $component_composer_json->getRelativePathname());
}
// Remind the user not to miss files in a patch.
if ($changed) {
$io->write("If you make a patch, ensure that the files above are included.");
}
}
/**
* Generate the component JSON files.
*
* @param \Composer\Script\Event $event
* The Composer event.
* @param string $component_pathname
* Relative path to the composer.json file for a component.
*
* @return bool
* TRUE if the generated component package is different from what is on
* disk.
*/
protected function generateComponentPackage(Event $event, string $component_pathname): bool {
$io = $event->getIO();
$composer_json_path = $this->componentBaseDir . '/' . $component_pathname;
$original_composer_json = file_exists($composer_json_path) ? file_get_contents($composer_json_path) : '';
// Modify the original data.
$composer_json_data = $this->getPackage($io, $original_composer_json);
$updated_composer_json = static::encode($composer_json_data);
// Exit early if nothing changed.
if (trim($original_composer_json, " \t\r\0\x0B") === trim($updated_composer_json, " \t\r\0\x0B")) {
return FALSE;
}
// Warn the user that a component file has been updated.
$display_path = static::$relativeComponentPath . '/' . $component_pathname;
$io->write("Updated component file <info>$display_path</info>.");
// Write the composer.json file back to disk.
$fs = new Filesystem();
$fs->ensureDirectoryExists(dirname($composer_json_path));
file_put_contents($composer_json_path, $updated_composer_json);
return TRUE;
}
/**
* Reconcile component dependencies with core.
*
* @param \Composer\IO\IOInterface $io
* IO object for messages to the user.
* @param string $original_json
* Contents of the component's composer.json file.
*
* @return array
* Structured data to be turned back into JSON.
*/
protected function getPackage(IOInterface $io, string $original_json): array {
$original_data = json_decode($original_json, TRUE);
$package_data = array_merge($original_data, $this->initialPackageMetadata());
$core_info = $this->drupalCoreInfo->rootComposerJson();
$stability = VersionParser::parseStability(\Drupal::VERSION);
// List of packages which we didn't find in either core requirement.
$not_in_core = [];
// Traverse required packages.
foreach (array_keys($original_data['require'] ?? []) as $package_name) {
// Reconcile locked constraints from drupal/drupal. We might have a locked
// version of a dependency that's not present in drupal/core.
if ($info = $this->drupalProjectInfo->packageLockInfo($package_name)) {
$package_data['require'][$package_name] = $info['version'];
}
// The package wasn't in the lock file, which means we need to tell the
// user. But there are some packages we want to exclude from this list.
elseif ($package_name !== 'php' && (strpos($package_name, 'drupal/core-') === FALSE)) {
$not_in_core[$package_name] = $package_name;
}
// Reconcile looser constraints from drupal/core, and we're totally OK
// with over-writing the locked ones from above.
if ($constraint = $core_info['require'][$package_name] ?? FALSE) {
$package_data['require'][$package_name] = $constraint;
}
// Reconcile dependencies on other Drupal components, so we can set the
// constraint to our current version.
if (strpos($package_name, 'drupal/core-') !== FALSE) {
if ($stability === 'stable') {
// Set the constraint to ^maj.min.
$package_data['require'][$package_name] = SemanticVersion::majorMinorConstraint(\Drupal::VERSION);
}
else {
// For non-stable releases, set the constraint to the branch version.
$package_data['require'][$package_name] = Composer::drupalVersionBranch();
// Also for non-stable releases which depend on another component,
// set the minimum stability. We do this so we can test build the
// components. Minimum-stability is otherwise ignored for packages
// which aren't the root package, so for any other purpose, this is
// unneeded.
$package_data['minimum-stability'] = $stability;
}
}
}
if ($not_in_core) {
$io->error($package_data['name'] . ' requires packages not present in drupal/drupal: ' . implode(', ', $not_in_core));
}
return $package_data;
}
/**
* Utility function to encode package json in a consistent way.
*
* @param array $composer_json_data
* Data to encode into a json string.
*
* @return string
* Encoded version of provided json data.
*/
public static function encode(array $composer_json_data): string {
return json_encode($composer_json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
/**
* Common default metadata for all components.
*
* @return array
* An array containing the common default metadata for all components.
*/
protected function initialPackageMetadata(): array {
return [
'extra' => [
'_readme' => [
'This file was partially generated automatically. See: https://www.drupal.org/node/3293830',
],
],
// Always reconcile PHP version.
'require' => [
'php' => '>=7.3.0',
],
];
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Drupal\Composer\Util;
/**
* Utility methods for manipulating semantic versions.
*/
class SemanticVersion {
/**
* Given a version, generate a loose ^major.minor constraint.
*
* @param string $version
* Semantic version string. Example: 9.5.0-beta23.
*
* @return string
* Constraint string for major and minor. Example: ^9.5
*/
public static function majorMinorConstraint(string $version): string {
preg_match('/^(\d+)\.(\d+)\.\d+/', $version, $matches);
return '^' . $matches[1] . '.' . $matches[2];
}
}

View File

@ -1,19 +1,27 @@
{ {
"name": "drupal/core-annotation", "name": "drupal/core-annotation",
"description": "Annotation discovery and implementation of plugins.", "description": "Annotation discovery and implementation of plugins.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"doctrine/annotations": "^1.4", "require": {
"drupal/core-file-cache": "^8.8", "php": ">=8.1.0",
"drupal/core-plugin": "^8.8", "doctrine/annotations": "^1.13",
"drupal/core-utility": "^8.8" "drupal/core-file-cache": "10.0.x-dev",
}, "drupal/core-plugin": "10.0.x-dev",
"autoload": { "drupal/core-utility": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\Annotation\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\Annotation\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-assertion", "name": "drupal/core-assertion",
"description": "Provides helper functionality for runtime assertions.", "description": "Provides helper functionality for runtime assertions.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\Assertion\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Assertion\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-class-finder", "name": "drupal/core-class-finder",
"description": "This class provides a class finding utility.", "description": "This class provides a class finding utility.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\ClassFinder\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\ClassFinder\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,24 @@
{ {
"name": "drupal/core-datetime", "name": "drupal/core-datetime",
"description": "This class wraps the PHP DateTime class with more flexible initialization parameters, allowing a date to be created from an existing date object, a timestamp, a string with an unknown format, a string with a known format, or an array of date parts. It also adds an errors array and a __toString() method to the date object.", "description": "This class wraps the PHP DateTime class with more flexible initialization parameters, allowing a date to be created from an existing date object, a timestamp, a string with an unknown format, a string with a known format, or an array of date parts. It also adds an errors array and a __toString() method to the date object.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"drupal/core-utility": "^8.8" "require": {
}, "php": ">=8.1.0",
"autoload": { "drupal/core-utility": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\Datetime\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\Datetime\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,26 +1,33 @@
{ {
"name": "drupal/core-dependency-injection", "name": "drupal/core-dependency-injection",
"description": "Dependency Injection container optimized for Drupal's needs.", "description": "Dependency Injection container optimized for Drupal's needs.",
"keywords": ["drupal", "dependency injection"], "keywords": [
"type": "library", "drupal",
"homepage": "https://www.drupal.org/project/drupal", "dependency injection"
"license": "GPL-2.0-or-later", ],
"support": { "type": "library",
"issues": "https://www.drupal.org/project/issues/drupal", "homepage": "https://www.drupal.org/project/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute", "license": "GPL-2.0-or-later",
"source": "https://www.drupal.org/project/drupal/git-instructions" "support": {
}, "issues": "https://www.drupal.org/project/issues/drupal",
"require": { "source": "https://www.drupal.org/project/drupal/git-instructions"
"php": ">=7.3.0", },
"symfony/dependency-injection": "^4.4", "require": {
"symfony/service-contracts": "^1.1|^2" "php": ">=8.1.0",
}, "symfony/dependency-injection": "^6.1",
"suggest": { "symfony/service-contracts": "v3.1.0"
"symfony/expression-language": "For using expressions in service container configuration" },
}, "suggest": {
"autoload": { "symfony/expression-language": "For using expressions in service container configuration"
"psr-4": { },
"Drupal\\Component\\DependencyInjection\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\DependencyInjection\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-diff", "name": "drupal/core-diff",
"description": "Diff.", "description": "Diff.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\Diff\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Diff\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,17 +1,25 @@
{ {
"name": "drupal/core-discovery", "name": "drupal/core-discovery",
"description": "Discovery.", "description": "Discovery.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"drupal/core-file-cache": "^8.8", "require": {
"drupal/core-serialization": "^8.8" "php": ">=8.1.0",
}, "drupal/core-file-cache": "10.0.x-dev",
"autoload": { "drupal/core-serialization": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\Discovery\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\Discovery\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,18 +1,25 @@
{ {
"name": "drupal/core-event-dispatcher", "name": "drupal/core-event-dispatcher",
"description": "EventDispatcher.", "description": "EventDispatcher.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"symfony/dependency-injection": "^4.4", "require": {
"symfony/event-dispatcher": "^4.4", "php": ">=8.1.0",
"symfony/event-dispatcher-contracts": "^1.1" "symfony/dependency-injection": "^6.1",
}, "symfony/event-dispatcher": "^6.1",
"autoload": { "symfony/event-dispatcher-contracts": "v3.1.0"
"psr-4": { },
"Drupal\\Component\\EventDispatcher\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\EventDispatcher\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-file-cache", "name": "drupal/core-file-cache",
"description": "FileCache.", "description": "FileCache.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\FileCache\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\FileCache\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-file-security", "name": "drupal/core-file-security",
"description": "FileSecurity.", "description": "FileSecurity.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\FileSecurity\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\FileSecurity\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-filesystem", "name": "drupal/core-filesystem",
"description": "FileSystem.", "description": "FileSystem.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\FileSystem\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\FileSystem\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,24 @@
{ {
"name": "drupal/core-front-matter", "name": "drupal/core-front-matter",
"description": "Component for parsing front matter from a source.", "description": "Component for parsing front matter from a source.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"drupal/core-serialization": "^8.8" "require": {
}, "php": ">=8.1.0",
"autoload": { "drupal/core-serialization": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\FrontMatter\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\FrontMatter\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,20 +1,25 @@
{ {
"name": "drupal/core-gettext", "name": "drupal/core-gettext",
"description": "PHP library for reading PO files.", "description": "PHP library for reading PO files.",
"type": "library", "type": "library",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"support": { "support": {
"issues": "https://www.drupal.org/project/issues/drupal", "issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute", "source": "https://www.drupal.org/project/drupal/git-instructions"
"source": "https://www.drupal.org/project/drupal/git-instructions" },
}, "require": {
"require": { "php": ">=8.1.0",
"php": ">=7.3.0", "drupal/core-render": "10.0.x-dev"
"drupal/core-render": "^9.4" },
}, "autoload": {
"autoload": { "psr-4": {
"psr-4": { "Drupal\\Component\\Gettext\\": ""
"Drupal\\Component\\Gettext\\": "" }
} },
} "extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-graph", "name": "drupal/core-graph",
"description": "Graph.", "description": "Graph.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\Graph\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Graph\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,23 @@
{ {
"name": "drupal/core-http-foundation", "name": "drupal/core-http-foundation",
"description": "HttpFoundation.", "description": "HttpFoundation.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"symfony/http-foundation": "^4.4" "require": {
}, "php": ">=8.1.0",
"autoload": { "symfony/http-foundation": "^6.1.1"
"psr-4": { },
"Drupal\\Component\\HttpFoundation\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\HttpFoundation\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,24 @@
{ {
"name": "drupal/core-php-storage", "name": "drupal/core-php-storage",
"description": "PhpStorage.", "description": "PhpStorage.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"drupal/core-file-security": "^8.8" "require": {
}, "php": ">=8.1.0",
"autoload": { "drupal/core-file-security": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\PhpStorage\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\PhpStorage\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,19 +1,28 @@
{ {
"name": "drupal/core-plugin", "name": "drupal/core-plugin",
"description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.", "description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.",
"keywords": ["drupal", "plugin", "plugins"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal",
"license": "GPL-2.0-or-later", "plugin",
"require": { "plugins"
"php": ">=7.3.0", ],
"symfony/validator": "^4.4" "homepage": "https://www.drupal.org/project/drupal",
}, "license": "GPL-2.0-or-later",
"autoload": { "require": {
"psr-4": { "php": ">=8.1.0",
"Drupal\\Component\\Plugin\\": "" "symfony/validator": "^6.1"
},
"autoload": {
"psr-4": {
"Drupal\\Component\\Plugin\\": ""
}
},
"suggest": {
"symfony/validator": "Leveraged in the use of context aware plugins."
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
},
"suggest": {
"symfony/validator": "Leveraged in the use of context aware plugins."
}
} }

View File

@ -1,15 +1,23 @@
{ {
"name": "drupal/core-proxy-builder", "name": "drupal/core-proxy-builder",
"description": "Provides a lightweight mechanism to provide lazy loaded proxies.", "description": "Provides a lightweight mechanism to provide lazy loaded proxies.",
"keywords": ["drupal", "proxy"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal",
"license": "GPL-2.0-or-later", "proxy"
"require": { ],
"php": ">=7.3.0" "homepage": "https://www.drupal.org/project/drupal",
}, "license": "GPL-2.0-or-later",
"autoload": { "require": {
"psr-4": { "php": ">=8.1.0"
"Drupal\\Component\\ProxyBuilder\\": "" },
"autoload": {
"psr-4": {
"Drupal\\Component\\ProxyBuilder\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,24 @@
{ {
"name": "drupal/core-render", "name": "drupal/core-render",
"description": "Renders placeholder variables for HTML and plain-text display.", "description": "Renders placeholder variables for HTML and plain-text display.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"drupal/core-utility": "^8.8" "require": {
}, "php": ">=8.1.0",
"autoload": { "drupal/core-utility": "10.0.x-dev"
"psr-4": { },
"Drupal\\Component\\Render\\": "" "autoload": {
} "psr-4": {
} "Drupal\\Component\\Render\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"minimum-stability": "dev"
} }

View File

@ -1,16 +1,23 @@
{ {
"name": "drupal/core-serialization", "name": "drupal/core-serialization",
"description": "Serialization.", "description": "Serialization.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0", "license": "GPL-2.0-or-later",
"symfony/yaml": "^4.4" "require": {
}, "php": ">=8.1.0",
"autoload": { "symfony/yaml": "^6.1"
"psr-4": { },
"Drupal\\Component\\Serialization\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Serialization\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,16 +1,23 @@
{ {
"name": "drupal/core-transliteration", "name": "drupal/core-transliteration",
"description": "Transliteration.", "description": "Transliteration.",
"type": "library", "type": "library",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"support": { "support": {
"issues": "https://www.drupal.org/project/issues/drupal", "issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute", "source": "https://www.drupal.org/project/drupal/git-instructions"
"source": "https://www.drupal.org/project/drupal/git-instructions" },
}, "autoload": {
"autoload": { "psr-4": {
"psr-4": { "Drupal\\Component\\Transliteration\\": ""
"Drupal\\Component\\Transliteration\\": "" }
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
},
"require": {
"php": ">=8.1.0"
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-utility", "name": "drupal/core-utility",
"description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.", "description": "Mostly static utility classes for string, xss, array, image, and other commonly needed manipulations.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\Utility\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Utility\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,19 +1,23 @@
{ {
"name": "drupal/core-uuid", "name": "drupal/core-uuid",
"description": "UUID generation and validation.", "description": "UUID generation and validation.",
"type": "library", "type": "library",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"support": { "support": {
"issues": "https://www.drupal.org/project/issues/drupal", "issues": "https://www.drupal.org/project/issues/drupal",
"irc": "irc://irc.freenode.net/drupal-contribute", "source": "https://www.drupal.org/project/drupal/git-instructions"
"source": "https://www.drupal.org/project/drupal/git-instructions" },
}, "require": {
"require": { "php": ">=8.1.0"
"php": ">=7.3.0" },
}, "autoload": {
"autoload": { "psr-4": {
"psr-4": { "Drupal\\Component\\Uuid\\": ""
"Drupal\\Component\\Uuid\\": "" }
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -1,15 +1,22 @@
{ {
"name": "drupal/core-version", "name": "drupal/core-version",
"description": "Utility classes for process Drupal specific version information.", "description": "Utility classes for process Drupal specific version information.",
"keywords": ["drupal"], "keywords": [
"homepage": "https://www.drupal.org/project/drupal", "drupal"
"license": "GPL-2.0-or-later", ],
"require": { "homepage": "https://www.drupal.org/project/drupal",
"php": ">=7.3.0" "license": "GPL-2.0-or-later",
}, "require": {
"autoload": { "php": ">=8.1.0"
"psr-4": { },
"Drupal\\Component\\Version\\": "" "autoload": {
"psr-4": {
"Drupal\\Component\\Version\\": ""
}
},
"extra": {
"_readme": [
"This file was partially generated automatically. See: https://www.drupal.org/node/3293830"
]
} }
}
} }

View File

@ -0,0 +1,86 @@
<?php
namespace Drupal\BuildTests\Composer\Component;
use Drupal\BuildTests\Composer\ComposerBuildTestBase;
use Drupal\Composer\Composer;
/**
* Try to install dependencies per component, using Composer.
*
* @group #slow
* @group Composer
* @group Component
*
* @coversNothing
*
* @requires externalCommand composer
*/
class ComponentsIsolatedBuildTest extends ComposerBuildTestBase {
/**
* Provides an array with relative paths to the component paths.
*
* @return array
* An array with relative paths to the component paths.
*/
public function provideComponentPaths(): array {
$data = [];
// During the dataProvider phase, there is not a workspace directory yet.
// So we will find relative paths and assemble them with the workspace
// path later.
$drupal_root = $this->getDrupalRoot();
$composer_json_finder = $this->getComponentPathsFinder($drupal_root);
/** @var \Symfony\Component\Finder\SplFileInfo $path */
foreach ($composer_json_finder->getIterator() as $path) {
$data[] = ['/' . $path->getRelativePath()];
}
return $data;
}
/**
* Test whether components' composer.json can be installed in isolation.
*
* @dataProvider provideComponentPaths
*/
public function testComponentComposerJson(string $component_path): void {
// Only copy the components. Copy all of them because some of them depend on
// each other.
$finder = $this->getCodebaseFinder();
$finder->in($this->getDrupalRoot() . static::$componentsPath);
$this->copyCodebase($finder->getIterator());
$working_dir = $this->getWorkingPath() . static::$componentsPath . $component_path;
// We add path repositories so we can wire internal dependencies together.
$this->addExpectedRepositories($working_dir);
// Perform the installation.
$this->executeCommand("composer install --working-dir=$working_dir --no-interaction --no-progress");
$this->assertCommandSuccessful();
}
/**
* Adds expected repositories as path repositories to package under test.
*
* @param string $working_dir
* The working directory.
*/
protected function addExpectedRepositories(string $working_dir): void {
$repo_paths = [
'Render' => 'drupal/core-render',
'Utility' => 'drupal/core-utility',
];
foreach ($repo_paths as $path => $package_name) {
$path_repo = $this->getWorkingPath() . static::$componentsPath . '/' . $path;
$repo_name = strtolower($path);
// Add path repositories with the current version number to the current
// package under test.
$drupal_version = Composer::drupalVersionBranch();
$this->executeCommand("composer config repositories.$repo_name " .
"'{\"type\": \"path\",\"url\": \"$path_repo\",\"options\": {\"versions\": {\"$package_name\": \"$drupal_version\"}}}' --working-dir=$working_dir");
}
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace Drupal\BuildTests\Composer\Component;
use Drupal\BuildTests\Composer\ComposerBuildTestBase;
use Drupal\Composer\Composer;
/**
* Demonstrate that the Component generator responds to release tagging.
*
* @group #slow
* @group Composer
* @group Component
*
* @coversNothing
*
* @requires externalCommand composer
*/
class ComponentsTaggedReleaseTest extends ComposerBuildTestBase {
/**
* Highly arbitrary version and constraint expectations.
*
* @return array
* - First element is the tag that should be applied to \Drupal::version.
* - Second element is the resulting constraint which should be present in
* the component core dependencies.
*/
public function providerVersionConstraint(): array {
return [
// [Tag, constraint]
'1.0.x-dev' => ['1.0.x-dev', '1.0.x-dev'],
'1.0.0-beta1' => ['1.0.0-beta1', '1.0.0-beta1'],
'1.0.0-rc1' => ['1.0.0-rc1', '1.0.0-rc1'],
'1.0.0' => ['1.0.0', '^1.0'],
];
}
/**
* Validate release tagging and regeneration of dependencies.
*
* @dataProvider providerVersionConstraint
*/
public function testReleaseTagging(string $tag, string $constraint): void {
$this->copyCodebase();
$drupal_root = $this->getWorkspaceDirectory();
// Set the core version.
Composer::setDrupalVersion($drupal_root, $tag);
$this->assertDrupalVersion($tag, $drupal_root);
// Emulate the release script.
// @see https://github.com/xjm/drupal_core_release/blob/main/tag.sh
$this->executeCommand("COMPOSER_ROOT_VERSION=\"$tag\" composer update drupal/core*");
$this->assertCommandSuccessful();
$this->assertErrorOutputContains('generateComponentPackages');
// Find all the components.
$component_finder = $this->getComponentPathsFinder($drupal_root);
// Loop through all the component packages.
/** @var \Symfony\Component\Finder\SplFileInfo $composer_json */
foreach ($component_finder->getIterator() as $composer_json) {
$composer_json_data = json_decode(file_get_contents($composer_json->getPathname()), TRUE);
$requires = array_merge(
$composer_json_data['require'] ?? [],
$composer_json_data['require-dev'] ?? []
);
// Required packages from drupal/core-* should have our constraint.
foreach ($requires as $package => $req_constraint) {
if (strpos($package, 'drupal/core-') !== FALSE) {
$this->assertEquals($constraint, $req_constraint);
}
}
}
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Drupal\BuildTests\Composer;
use Drupal\BuildTests\Framework\BuildTestBase;
use Symfony\Component\Finder\Finder;
/**
* Base class for Composer build tests.
*
* @coversNothing
*/
abstract class ComposerBuildTestBase extends BuildTestBase {
/**
* Relative path from Drupal root to the Components directory.
*
* @var string
*/
protected static $componentsPath = '/core/lib/Drupal/Component';
/**
* Assert that the VERSION constant in Drupal.php is the expected value.
*
* @param string $expectedVersion
* The expected version.
* @param string $dir
* The path to the site root.
*
* @internal
*/
protected function assertDrupalVersion(string $expectedVersion, string $dir): void {
$drupal_php_path = $dir . '/core/lib/Drupal.php';
$this->assertFileExists($drupal_php_path);
// Read back the Drupal version that was set and assert it matches
// expectations
$this->executeCommand("php -r 'include \"$drupal_php_path\"; print \Drupal::VERSION;'");
$this->assertCommandSuccessful();
$this->assertCommandOutputContains($expectedVersion);
}
/**
* Find all the composer.json files for components.
*
* @param string $drupal_root
* The Drupal root directory.
*
* @return \Symfony\Component\Finder\Finder
* A Finder object with all the composer.json files for components.
*/
protected function getComponentPathsFinder(string $drupal_root): Finder {
$finder = new Finder();
$finder->name('composer.json')
->in($drupal_root . static::$componentsPath)
->ignoreUnreadableDirs()
->depth(1);
return $finder;
}
}

View File

@ -4,7 +4,7 @@ namespace Drupal\BuildTests\Composer\Template;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
use Drupal\BuildTests\Framework\BuildTestBase; use Drupal\BuildTests\Composer\ComposerBuildTestBase;
use Drupal\Composer\Composer; use Drupal\Composer\Composer;
/** /**
@ -25,7 +25,7 @@ use Drupal\Composer\Composer;
* *
* @requires externalCommand composer * @requires externalCommand composer
*/ */
class ComposerProjectTemplatesTest extends BuildTestBase { class ComposerProjectTemplatesTest extends ComposerBuildTestBase {
/** /**
* The minimum stability requirement for dependencies. * The minimum stability requirement for dependencies.
@ -297,26 +297,6 @@ class ComposerProjectTemplatesTest extends BuildTestBase {
} }
} }
/**
* Assert that the VERSION constant in Drupal.php is the expected value.
*
* @param string $expectedVersion
* The expected version.
* @param string $dir
* The path to the site root.
*
* @internal
*/
protected function assertDrupalVersion(string $expectedVersion, string $dir): void {
$drupal_php_path = $dir . '/core/lib/Drupal.php';
$this->assertFileExists($drupal_php_path);
// Read back the Drupal version that was set and assert it matches expectations.
$this->executeCommand("php -r 'include \"$drupal_php_path\"; print \Drupal::VERSION;'");
$this->assertCommandSuccessful();
$this->assertCommandOutputContains($expectedVersion);
}
/** /**
* Creates a test package that points to the templates. * Creates a test package that points to the templates.
* *