diff --git a/composer.json b/composer.json index f99913123e..569097373e 100644 --- a/composer.json +++ b/composer.json @@ -71,11 +71,15 @@ } }, "scripts": { - "pre-install-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion", - "pre-update-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion", + "pre-install-cmd": "Drupal\\Composer\\Composer::ensureComposerVersion", + "pre-update-cmd": "Drupal\\Composer\\Composer::ensureComposerVersion", "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump", "drupal-phpunit-upgrade-check": "Drupal\\Core\\Composer\\Composer::upgradePHPUnit", "drupal-phpunit-upgrade": "@composer update phpunit/phpunit symfony/phpunit-bridge phpspec/prophecy symfony/yaml --with-dependencies --no-progress", + "post-update-cmd": [ + "Drupal\\Composer\\Composer::generateMetapackages", + "Drupal\\Composer\\Composer::ensureBehatDriverVersions" + ], "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --", "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --" }, diff --git a/composer.lock b/composer.lock index e30fe37532..de56d61d07 100644 --- a/composer.lock +++ b/composer.lock @@ -947,14 +947,14 @@ "authors": [ { "name": "Nicholas Humfrey", - "role": "Developer", "email": "njh@aelius.com", - "homepage": "http://www.aelius.com/njh/" + "homepage": "http://www.aelius.com/njh/", + "role": "Developer" }, { "name": "Alexey Zakhlestin", - "role": "Developer", - "email": "indeyets@gmail.com" + "email": "indeyets@gmail.com", + "role": "Developer" } ], "description": "EasyRdf is a PHP library designed to make it easy to consume and produce RDF.", @@ -4016,16 +4016,16 @@ }, { "name": "behat/mink-selenium2-driver", - "version": "dev-master", + "version": "1.3.x-dev", "source": { "type": "git", "url": "https://github.com/minkphp/MinkSelenium2Driver.git", - "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a" + "reference": "0a09c4341621fca937a726827611b20ce3e2c259" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/8684ee4e634db7abda9039ea53545f86fc1e105a", - "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a", + "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/0a09c4341621fca937a726827611b20ce3e2c259", + "reference": "0a09c4341621fca937a726827611b20ce3e2c259", "shasum": "" }, "require": { @@ -4052,15 +4052,15 @@ "MIT" ], "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, { "name": "Pete Otaqui", "email": "pete@otaqui.com", "homepage": "https://github.com/pete-otaqui" + }, + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" } ], "description": "Selenium2 (WebDriver) driver for Mink framework", @@ -4073,7 +4073,7 @@ "testing", "webdriver" ], - "time": "2018-10-10T12:39:06+00:00" + "time": "2019-09-02T09:46:54+00:00" }, { "name": "composer/ca-bundle", @@ -4874,18 +4874,18 @@ "authors": [ { "name": "Arne Blankerts", - "role": "Developer", - "email": "arne@blankerts.de" + "email": "arne@blankerts.de", + "role": "Developer" }, { "name": "Sebastian Heuer", - "role": "Developer", - "email": "sebastian@phpeople.de" + "email": "sebastian@phpeople.de", + "role": "Developer" }, { "name": "Sebastian Bergmann", - "role": "Developer", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "Developer" } ], "description": "Library for handling version information and constraints", @@ -5244,8 +5244,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "Simple template engine.", @@ -6048,8 +6048,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", diff --git a/composer/Composer.php b/composer/Composer.php new file mode 100644 index 0000000000..0e2e943794 --- /dev/null +++ b/composer/Composer.php @@ -0,0 +1,81 @@ +generate($event->getIO(), getcwd()); + } + + /** + * Ensure that the minimum required version of Composer is running. + * Throw an exception if Composer is too old. + */ + public static function ensureComposerVersion() { + $composerVersion = method_exists(ComposerApp::class, 'getVersion') ? + ComposerApp::getVersion() : ComposerApp::VERSION; + if (Comparator::lessThan($composerVersion, '1.9.0')) { + throw new \RuntimeException("Drupal core development requires Composer 1.9.0, but Composer $composerVersion is installed. Please run 'composer self-update'."); + } + } + + /** + * Ensure that the right version of behat/mink-selenium2-driver is locked. + * Throw an exception if we do not have 1.3.x-dev. + * + * @todo: Remove this once we have a stable release. + */ + public static function ensureBehatDriverVersions() { + $drupalCoreComposer = DrupalCoreComposer::createFromPath(getcwd()); + + $expectedVersion = '1.3.x-dev'; + $behatMinkSelenium2DriverInfo = $drupalCoreComposer->packageLockInfo('behat/mink-selenium2-driver', TRUE); + if ($behatMinkSelenium2DriverInfo['version'] != $expectedVersion) { + $drupalVersion = static::drupalVersionBranch(); + $message = <<< __EOT__ +Drupal requires behat/mink-selenium2-driver:$expectedVersion in its composer.json +file, but it is pinned to {$behatMinkSelenium2DriverInfo['version']} in the composer.lock file. +This sometimes happens when Composer becomes confused. To fix: + +1. `git checkout -- composer.lock`, or otherwise reset to a known-good lock file. +2. `rm -rf vendor` +3. `composer install` +4. `COMPOSER_ROOT_VERSION={$drupalVersion} composer update ...` (where ... is + the update arguments you wish to run, e.g. --lock). +__EOT__; + throw new \RuntimeException($message); + } + } + + /** + * Return the branch name the current Drupal version is associated with. + * + * @return string + * A branch name, e.g. 8.9.x or 9.0.x. + */ + public static function drupalVersionBranch() { + return preg_replace('#\.[0-9]+-dev#', '.x-dev', \Drupal::VERSION); + } + +} diff --git a/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php b/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php new file mode 100644 index 0000000000..7964f0610b --- /dev/null +++ b/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php @@ -0,0 +1,68 @@ +initialPackageMetadata(); + + // Pull up the composer lock data. + $composerLockData = $this->drupalCoreInfo->composerLock(); + if (!isset($composerLockData['packages'])) { + return $composer; + } + + // Make a list of packages we do not want to put in the 'require' section. + $remove_list = ['drupal/core', 'wikimedia/composer-merge-plugin']; + + // Copy the 'packages' section from the Composer lock into our 'require' + // section. There is also a 'packages-dev' section, but we do not need + // to pin 'require-dev' versions, as 'require-dev' dependencies are never + // included from subprojects. Use 'drupal/core-dev-dependencies' to get + // Drupal's dev dependencies. + foreach ($composerLockData['packages'] as $package) { + // If there is no 'source' record, then this is a path repository + // or something else that we do not want to include. + if (isset($package['source']) && !in_array($package['name'], $remove_list)) { + $composer['require'][$package['name']] = $package['version']; + } + } + return $composer; + } + + /** + * Returns the initial package metadata that describes the metapackage. + * + * @return array + */ + protected function initialPackageMetadata() { + return [ + "name" => "drupal/core-recommended", + "type" => "metapackage", + "description" => "Locked core dependencies; require this project INSTEAD OF drupal/core.", + "license" => "GPL-2.0-or-later", + "conflict" => [ + "webflo/drupal-core-strict" => "*", + ], + "require" => [ + "drupal/core" => "self.version", + ], + ]; + } + +} diff --git a/composer/Generator/Builder/DrupalDevDependenciesBuilder.php b/composer/Generator/Builder/DrupalDevDependenciesBuilder.php new file mode 100644 index 0000000000..0b68b4a0e4 --- /dev/null +++ b/composer/Generator/Builder/DrupalDevDependenciesBuilder.php @@ -0,0 +1,62 @@ +initialPackageMetadata(); + + // Put everything from Drupal's "require-dev" into our "require" section. + $composer['require'] = $this->drupalCoreInfo->getRequireDev(); + + // If the require-dev is bringing in a dev version of behat/mink, convert + // the requirement to a more flexible set of versions. + // @todo: remove when https://www.drupal.org/node/3078671 is fixed. + if (isset($composer['require']['behat/mink']) && ($composer['require']['behat/mink'] == '1.7.x-dev')) { + $composer['require']['behat/mink'] = '1.8.0 | 1.7.1.1 | 1.7.x-dev'; + } + + // Do the same sort of conversion for behat/mink-selenium2-driver. + if (isset($composer['require']['behat/mink-selenium2-driver']) && ($composer['require']['behat/mink-selenium2-driver'] == '1.3.x-dev')) { + $composer['require']['behat/mink-selenium2-driver'] = '1.4.0 | 1.3.1.1 | 1.3.x-dev'; + } + + // Sort our required packages by key. + ksort($composer['require']); + + return $composer; + } + + /** + * Returns the initial package metadata that describes the metapackage. + * + * @return array + */ + protected function initialPackageMetadata() { + return [ + "name" => "drupal/dev-dependencies", + "type" => "metapackage", + "description" => "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", + "license" => "GPL-2.0-or-later", + "conflict" => [ + "webflo/drupal-core-require-dev" => "*", + ], + ]; + } + +} diff --git a/composer/Generator/Builder/DrupalPackageBuilder.php b/composer/Generator/Builder/DrupalPackageBuilder.php new file mode 100644 index 0000000000..6c08721647 --- /dev/null +++ b/composer/Generator/Builder/DrupalPackageBuilder.php @@ -0,0 +1,30 @@ +drupalCoreInfo = $drupalCoreInfo; + } + +} diff --git a/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php b/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php new file mode 100644 index 0000000000..4a4abb3c1c --- /dev/null +++ b/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php @@ -0,0 +1,69 @@ +initialPackageMetadata(); + + // Pull the exact versions of the dependencies from the composer.lock + // file and use it to build our 'require' section. + $composerLockData = $this->drupalCoreInfo->composerLock(); + + if (isset($composerLockData['packages-dev'])) { + + foreach ($composerLockData['packages-dev'] as $package) { + $composer['require'][$package['name']] = $package['version']; + + // If the require-dev is bringing in a dev version of behat/mink, + // convert the requirement to a more flexible set of versions. + // @todo: remove when https://www.drupal.org/node/3078671 is fixed. + if (($package['name'] == 'behat/mink') && (($package['version'] == 'dev-master') || ($package['version'] == '1.7.x-dev'))) { + $composer['require']['behat/mink'] = '1.8.0 | 1.7.1.1 | 1.7.x-dev'; + } + + // Do the same sort of conversion for behat/mink-selenium2-driver. + if (($package['name'] == 'behat/mink-selenium2-driver') && (($package['version'] == 'dev-master') || ($package['version'] == '1.3.x-dev'))) { + $composer['require']['behat/mink-selenium2-driver'] = '1.4.0 | 1.3.1.1 | 1.3.x-dev'; + } + } + } + return $composer; + } + + /** + * Returns the initial package metadata that describes the metapackage. + * + * @return array + */ + protected function initialPackageMetadata() { + return [ + "name" => "drupal/pinned-dev-dependencies", + "type" => "metapackage", + "description" => "Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", + "license" => "GPL-2.0-or-later", + "conflict" => [ + "webflo/drupal-core-require-dev" => "*", + ], + "require" => [ + "drupal/core" => "self.version", + ], + ]; + } + +} diff --git a/composer/Generator/BuilderInterface.php b/composer/Generator/BuilderInterface.php new file mode 100644 index 0000000000..de4ddf8cc0 --- /dev/null +++ b/composer/Generator/BuilderInterface.php @@ -0,0 +1,44 @@ +generatedProjectBaseDir = dirname(__DIR__) . '/Metapackage'; + } + + /** + * Generate Drupal's metapackages whenever composer.lock is updated + * + * @param \Composer\IO\IOInterface $io + * Composer IO object for interacting with the user. + * @param string $base_dir + * Directory where drupal/drupal repository is located. + */ + public function generate(IOInterface $io, $base_dir) { + // General information from drupal/drupal and drupal/core composer.json + // and composer.lock files. + $drupalCoreInfo = DrupalCoreComposer::createFromPath($base_dir); + + // Exit early if there is no composer.lock file. + if (empty($drupalCoreInfo->composerLock())) { + return; + } + + // Run all of our available builders. + $builders = $this->builders(); + $changed = FALSE; + foreach ($builders as $builder_class) { + $builder = new $builder_class($drupalCoreInfo); + $changed |= $this->generateMetapackage($io, $builder); + } + + // 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."); + } + } + + /** + * Returns a list of metapackage builders. + * + * @return BuilderInterface[] + */ + protected function builders() { + return [ + DrupalCoreRecommendedBuilder::class, + DrupalDevDependenciesBuilder::class, + DrupalPinnedDevDependenciesBuilder::class, + ]; + } + + /** + * Generate one metapackage. + * + * @param \Composer\IO\IOInterface $io + * Composer IO object for interacting with the user. + * @param BuilderInterface $builder + * An object that can build a metapackage. + * + * @return bool + * TRUE if the generated metapackage is different than what is on disk. + */ + protected function generateMetapackage(IOInterface $io, BuilderInterface $builder) { + + // Load the existing composer.json file for drupal/core-recommended + $relative_path = $builder->getPath() . '/composer.json'; + $composer_json_path = $this->generatedProjectBaseDir . '/' . $relative_path; + $original_composer_json = file_exists($composer_json_path) ? file_get_contents($composer_json_path) : ''; + + // Get the composer.json file from the builder. + $composer_json_data = $builder->getPackage(); + $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 metapackage file has been updated.. + $io->write("Updated metapackage file composer/Metapackage/$relative_path."); + + // 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; + } + + /** + * Utility function to encode metapackage 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($composer_json_data) { + return json_encode($composer_json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + } + +} diff --git a/composer/Generator/TESTING.txt b/composer/Generator/TESTING.txt new file mode 100644 index 0000000000..59ccd78314 --- /dev/null +++ b/composer/Generator/TESTING.txt @@ -0,0 +1,18 @@ +HOW-TO: Test this Drupal composer script + +In order to test this script, you'll need to get the entire Drupal repo and +run the tests there. + +You'll find the tests under core/tests/Drupal/Tests/Composer/Generator. + +You can get the full Drupal repo here: +https://www.drupal.org/project/drupal/git-instructions + +You can find more information about running PHPUnit tests with Drupal here: +https://www.drupal.org/node/2116263 + +Each component in the Drupal\Composer\Plugin namespace has its own annotated test +group. You can use this group to run only the tests for this component. Like +this: + +$ ./vendor/bin/phpunit -c core --group Metapackage diff --git a/composer/Generator/Util/DrupalCoreComposer.php b/composer/Generator/Util/DrupalCoreComposer.php new file mode 100644 index 0000000000..1e063ceb15 --- /dev/null +++ b/composer/Generator/Util/DrupalCoreComposer.php @@ -0,0 +1,120 @@ +composerJson = $composerJson; + $this->composerLock = $composerLock; + } + + /** + * DrupalCoreComposer factory. + * + * @param string $repositoryPath + * Path to a directory containing a composer.json and composer.lock files. + */ + public static function createFromPath(string $repositoryPath) { + $composerJson = static::loadJsonFromPath("$repositoryPath/composer.json"); + $composerLock = static::loadJsonFromPath("$repositoryPath/composer.lock"); + return new self($composerJson, $composerLock); + } + + /** + * Fetch the composer data from the root drupal/drupal project. + * + * @return array + * Composer json data + */ + public function rootComposerJson() { + return $this->composerJson; + } + + /** + * Fetch the composer lock data. + * + * @return array + * Composer lock data + */ + public function composerLock() { + return $this->composerLock; + } + + /** + * Return the "require-dev" section from root or core composer.json file. + * + * The require-dev constraints moved from core/composer.json (8.7.x and + * earlier) to the root composer.json file (8.8.x and later). + * + * @return array + * The contents of the "require-dev" section. + */ + public function getRequireDev() { + $composerJsonData = $this->rootComposerJson(); + return isset($composerJsonData['require-dev']) ? $composerJsonData['require-dev'] : []; + } + + /** + * Look up the info for one package in the composer.lock file. + * + * @param string $packageName + * Name of package to find, e.g. 'behat/mink-selenium2-driver'. + * @param bool $dev + * TRUE: consider only 'packages-dev'. Default: consider only 'packages' + * + * @return array + * Package info from composer.lock. + */ + public function packageLockInfo($packageName, $dev = FALSE) { + $packagesSection = $dev ? 'packages-dev' : 'packages'; + foreach ($this->composerLock[$packagesSection] as $info) { + if ($info['name'] == $packageName) { + return $info; + } + } + return []; + } + + /** + * Load json data from the specified path. + * + * @param string $path + * Relative path to the json file to load. + * + * @return array + * The contents of the json data for the specified file. + */ + protected static function loadJsonFromPath($path) { + return file_exists($path) ? json_decode(file_get_contents($path), TRUE) : []; + } + +} diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json new file mode 100644 index 0000000000..d014c9198f --- /dev/null +++ b/composer/Metapackage/CoreRecommended/composer.json @@ -0,0 +1,69 @@ +{ + "name": "drupal/core-recommended", + "type": "metapackage", + "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.", + "license": "GPL-2.0-or-later", + "conflict": { + "webflo/drupal-core-strict": "*" + }, + "require": { + "drupal/core": "self.version", + "asm89/stack-cors": "1.2.0", + "composer/installers": "v1.7.0", + "composer/semver": "1.5.0", + "doctrine/annotations": "v1.4.0", + "doctrine/cache": "v1.6.2", + "doctrine/collections": "v1.4.0", + "doctrine/common": "v2.7.3", + "doctrine/inflector": "v1.2.0", + "doctrine/lexer": "1.0.2", + "easyrdf/easyrdf": "0.9.1", + "egulias/email-validator": "2.1.11", + "guzzlehttp/guzzle": "6.3.3", + "guzzlehttp/promises": "v1.3.1", + "guzzlehttp/psr7": "1.6.1", + "masterminds/html5": "2.7.0", + "pear/archive_tar": "1.4.8", + "pear/console_getopt": "v1.4.2", + "pear/pear-core-minimal": "v1.10.9", + "pear/pear_exception": "v1.0.0", + "psr/container": "1.0.0", + "psr/http-message": "1.0.1", + "psr/log": "1.1.0", + "ralouphie/getallheaders": "3.0.3", + "stack/builder": "v1.0.5", + "symfony-cmf/routing": "2.1.0", + "symfony/class-loader": "v3.4.32", + "symfony/console": "4.4.x-dev", + "symfony/debug": "4.4.x-dev", + "symfony/dependency-injection": "4.4.x-dev", + "symfony/error-handler": "dev-master", + "symfony/error-renderer": "4.4.x-dev", + "symfony/event-dispatcher": "4.4.x-dev", + "symfony/event-dispatcher-contracts": "v1.1.7", + "symfony/http-foundation": "4.4.x-dev", + "symfony/http-kernel": "4.4.x-dev", + "symfony/mime": "v4.3.5", + "symfony/polyfill-ctype": "v1.12.0", + "symfony/polyfill-iconv": "v1.12.0", + "symfony/polyfill-intl-idn": "v1.12.0", + "symfony/polyfill-mbstring": "v1.12.0", + "symfony/polyfill-php72": "v1.12.0", + "symfony/polyfill-php73": "v1.12.0", + "symfony/process": "4.4.x-dev", + "symfony/psr-http-message-bridge": "v1.2.0", + "symfony/routing": "4.4.x-dev", + "symfony/serializer": "4.4.x-dev", + "symfony/service-contracts": "v1.1.7", + "symfony/translation": "4.4.x-dev", + "symfony/translation-contracts": "v1.1.7", + "symfony/validator": "4.4.x-dev", + "symfony/yaml": "4.4.x-dev", + "twig/twig": "v1.42.3", + "typo3/phar-stream-wrapper": "v3.1.3", + "zendframework/zend-diactoros": "1.7.2", + "zendframework/zend-escaper": "2.6.1", + "zendframework/zend-feed": "2.12.0", + "zendframework/zend-stdlib": "3.2.1" + } +} diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json new file mode 100644 index 0000000000..586a4e8309 --- /dev/null +++ b/composer/Metapackage/DevDependencies/composer.json @@ -0,0 +1,30 @@ +{ + "name": "drupal/dev-dependencies", + "type": "metapackage", + "description": "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", + "license": "GPL-2.0-or-later", + "conflict": { + "webflo/drupal-core-require-dev": "*" + }, + "require": { + "behat/mink": "1.8.0 | 1.7.1.1 | 1.7.x-dev", + "behat/mink-goutte-driver": "^1.2", + "behat/mink-selenium2-driver": "1.4.0 | 1.3.1.1 | 1.3.x-dev", + "composer/composer": "^1.8", + "drupal/coder": "^8.3.2", + "jcalderonzumba/gastonjs": "^1.0.2", + "jcalderonzumba/mink-phantomjs-driver": "^0.3.1", + "justinrainbow/json-schema": "^5.2", + "mikey179/vfsstream": "^1.2", + "phpspec/prophecy": "^1.7", + "phpunit/phpunit": "^6.5 || ^7", + "symfony/browser-kit": "^4.4", + "symfony/css-selector": "^4.4", + "symfony/debug": "^4.4", + "symfony/dom-crawler": "^4.4", + "symfony/filesystem": "^4.4", + "symfony/finder": "^4.4", + "symfony/lock": "^4.4", + "symfony/phpunit-bridge": "^4.4" + } +} diff --git a/composer/Metapackage/PinnedDevDependencies/composer.json b/composer/Metapackage/PinnedDevDependencies/composer.json new file mode 100644 index 0000000000..fb040b49f4 --- /dev/null +++ b/composer/Metapackage/PinnedDevDependencies/composer.json @@ -0,0 +1,65 @@ +{ + "name": "drupal/pinned-dev-dependencies", + "type": "metapackage", + "description": "Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", + "license": "GPL-2.0-or-later", + "conflict": { + "webflo/drupal-core-require-dev": "*" + }, + "require": { + "drupal/core": "self.version", + "behat/mink": "1.8.0 | 1.7.1.1 | 1.7.x-dev", + "behat/mink-browserkit-driver": "1.3.3", + "behat/mink-goutte-driver": "v1.2.1", + "behat/mink-selenium2-driver": "1.4.0 | 1.3.1.1 | 1.3.x-dev", + "composer/ca-bundle": "1.2.4", + "composer/composer": "1.9.0", + "composer/spdx-licenses": "1.5.2", + "composer/xdebug-handler": "1.3.3", + "doctrine/instantiator": "1.0.5", + "drupal/coder": "8.3.6", + "fabpot/goutte": "v3.2.3", + "instaclick/php-webdriver": "1.4.6", + "jcalderonzumba/gastonjs": "v1.0.2", + "jcalderonzumba/mink-phantomjs-driver": "v0.3.2", + "justinrainbow/json-schema": "5.2.8", + "mikey179/vfsstream": "v1.6.7", + "myclabs/deep-copy": "1.7.0", + "phar-io/manifest": "1.0.1", + "phar-io/version": "1.0.1", + "phpdocumentor/reflection-common": "1.0.1", + "phpdocumentor/reflection-docblock": "4.3.2", + "phpdocumentor/type-resolver": "0.5.1", + "phpspec/prophecy": "1.9.0", + "phpunit/php-code-coverage": "5.3.2", + "phpunit/php-file-iterator": "1.4.5", + "phpunit/php-text-template": "1.2.1", + "phpunit/php-timer": "1.0.9", + "phpunit/php-token-stream": "2.0.2", + "phpunit/phpunit": "6.5.14", + "phpunit/phpunit-mock-objects": "5.0.10", + "sebastian/code-unit-reverse-lookup": "1.0.1", + "sebastian/comparator": "2.1.3", + "sebastian/diff": "2.0.1", + "sebastian/environment": "3.1.0", + "sebastian/exporter": "3.1.2", + "sebastian/global-state": "2.0.0", + "sebastian/object-enumerator": "3.0.3", + "sebastian/object-reflector": "1.1.1", + "sebastian/recursion-context": "3.0.0", + "sebastian/resource-operations": "1.0.0", + "sebastian/version": "2.0.1", + "seld/jsonlint": "1.7.1", + "seld/phar-utils": "1.0.1", + "squizlabs/php_codesniffer": "3.5.0", + "symfony/browser-kit": "4.4.x-dev", + "symfony/css-selector": "4.4.x-dev", + "symfony/dom-crawler": "4.4.x-dev", + "symfony/filesystem": "4.4.x-dev", + "symfony/finder": "4.4.x-dev", + "symfony/lock": "4.4.x-dev", + "symfony/phpunit-bridge": "4.4.x-dev", + "theseer/tokenizer": "1.1.3", + "webmozart/assert": "1.5.0" + } +} diff --git a/composer/Metapackage/README.txt b/composer/Metapackage/README.txt new file mode 100644 index 0000000000..440e89e0a9 --- /dev/null +++ b/composer/Metapackage/README.txt @@ -0,0 +1,66 @@ +# Drupal Metapackages + +A metapackage is a Composer package that contains only a composer.json, and +has no other content. In other words, the purpose of a metapackage is to +provide dependencies, not to provide code or data. + + +## Metapackages Provided by Drupal Core + +Drupal Core provides three metapackages that serve different purposes. + + - drupal/core-recommended: This project pins to the exact version of each + dependency used in drupal/core. It also requires drupal/core, so + drupal/core-recommended should be used INSTEAD OF drupal/core. See usage + diagram below. This relationship makes it easier for Composer to update + a Drupal project. + + - drupal/dev-dependencies: This project provides the same version constraints + as Drupal uses for testing. It is useful for projects that either wish to + run some of the Drupal tests directly, or for projects that may wish to use + the same components that Drupal does for testing. + + - drupal/pinned-dev-dependencies: This project should be used INSTEAD OF + drupal/dev-dependencies in instances where a project wishes to pin to the + exact version of each testing dependency used in Drupal. This in general + should not be necessary. + +Note that a project that uses both drupal/core-recommended and +drupal/pinned-dev-dependencies must update them both at the same time, e.g.: + + composer update drupal/core-recommended drupal/pinned-dev-dependencies --with-updates + +Composer may have trouble with the update if one of these projects is listed +on the command line without the other. Running composer update without any +parameters should also work, because in this instance every dependency is +updated. + + +## Metapackage Usage in Template Projects + +The relationship between the metapackages drupal/core-recommended and +drupal/dev-dependencies and the project (subtree split) drupal/core, as +used in the drupal/recommended-project is shown below: + ++----------------------------+ +| drupal/recommended-project | ++----------------------------+ + | + +--"require": + | | + | | +-------------------------+ +-------------+ + | +-->| drupal/core-recommended |-->| drupal/core | + | +-------------------------+ +-------------+ + | + +--"require-dev": + | + | +-------------------------+ + +-->| drupal/dev-dependencies | + +-------------------------+ + +If a user does not wish to pin their Drupal project's dependencies to the same +versions used in drupal/core, then they should replace drupal/core-recommended +with drupal/core in their "require" section. + +If a user does not need the testing dependencies in their Drupal project, then +they may simply remove drupal/dev-dependencies from the "require-dev" section. diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php index 627972c21c..453a2485f6 100644 --- a/core/lib/Drupal/Core/Composer/Composer.php +++ b/core/lib/Drupal/Core/Composer/Composer.php @@ -2,10 +2,8 @@ namespace Drupal\Core\Composer; -use Composer\Composer as ComposerApp; use Composer\Installer\PackageEvent; use Composer\Script\Event; -use Composer\Semver\Comparator; use Composer\Semver\Constraint\Constraint; use Composer\Util\ProcessExecutor; use Drupal\Component\FileSecurity\FileSecurity; @@ -96,20 +94,10 @@ class Composer { 'zendframework/zend-stdlib' => ['doc'], ]; - /** - * Ensure that the minimum required version of Composer is running. - * Throw an exception if Composer is too old. - */ - public static function ensureComposerVersion() { - $composerVersion = method_exists(ComposerApp::class, 'getVersion') ? - ComposerApp::getVersion() : ComposerApp::VERSION; - if (Comparator::lessThan($composerVersion, '1.9.0')) { - throw new \RuntimeException("Drupal core development requires Composer 1.9.0, but Composer $composerVersion is installed. Please run 'composer self-update'."); - } - } - /** * Add vendor classes to Composer's static classmap. + * + * @param \Composer\Script\Event $event */ public static function preAutoloadDump(Event $event) { // Get the configured vendor directory. diff --git a/core/tests/Drupal/Tests/Composer/ComposerTest.php b/core/tests/Drupal/Tests/Composer/ComposerTest.php new file mode 100644 index 0000000000..7728187888 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/ComposerTest.php @@ -0,0 +1,47 @@ +assertNull(Composer::ensureComposerVersion()); + } + catch (\RuntimeException $e) { + $this->assertRegExp('/Drupal core development requires Composer 1.9.0, but Composer /', $e->getMessage()); + } + } + + /** + * Verify that Composer::ensureBehatDriverVersions() detects a good version. + * + * @covers::ensureBehatDriverVersions + */ + public function testEnsureBehatDriverVersions() { + // First call 'ensureBehatDriverVersions' test directly using Drupal's + // composer.lock. It should not fail. + chdir($this->root); + $this->assertNull(Composer::ensureBehatDriverVersions()); + + // Next, call 'ensureBehatDriverVersions' again, this time using a fixture + // with a known-bad version number. + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageRegExp('#^Drupal requires behat/mink-selenium2-driver:1.3.x-dev#'); + chdir(__DIR__ . '/fixtures/ensureBehatDriverVersionsFixture'); + $this->assertNull(Composer::ensureBehatDriverVersions()); + } + +} diff --git a/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php b/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php new file mode 100644 index 0000000000..21abeca07c --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php @@ -0,0 +1,101 @@ + 'drupal/core-recommended', + 'type' => 'metapackage', + 'description' => 'Locked core dependencies; require this project INSTEAD OF drupal/core.', + 'license' => 'GPL-2.0-or-later', + 'require' => + [ + 'drupal/core' => 'self.version', + 'symfony/polyfill-ctype' => 'v1.12.0', + 'symfony/yaml' => 'v3.4.32', + ], + 'conflict' => + [ + 'webflo/drupal-core-strict' => '*', + ], + ], + ], + + [ + DrupalDevDependenciesBuilder::class, + 'Drupal', + [ + 'name' => 'drupal/dev-dependencies', + 'type' => 'metapackage', + 'description' => 'require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.', + 'license' => 'GPL-2.0-or-later', + 'require' => + [ + 'behat/mink' => '1.8.0 | 1.7.1.1 | 1.7.x-dev', + ], + 'conflict' => + [ + 'webflo/drupal-core-require-dev' => '*', + ], + ], + ], + + [ + DrupalPinnedDevDependenciesBuilder::class, + 'Drupal', + [ + 'name' => 'drupal/pinned-dev-dependencies', + 'type' => 'metapackage', + 'description' => 'Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.', + 'license' => 'GPL-2.0-or-later', + 'require' => + [ + 'drupal/core' => 'self.version', + 'behat/mink' => '1.8.0 | 1.7.1.1 | 1.7.x-dev', + 'symfony/css-selector' => 'v4.3.5', + ], + 'conflict' => + [ + 'webflo/drupal-core-require-dev' => '*', + ], + ], + ], + + ]; + } + + /** + * Test all of the various kinds of builders. + * + * @dataProvider builderTestData + */ + public function testBuilder($builderClass, $referenceName, $expected) { + $fixtures = new Fixtures(); + $drupalCoreInfo = $fixtures->drupalCoreComposerFixture($referenceName); + + $builder = new $builderClass($drupalCoreInfo); + $generatedJson = $builder->getPackage(); + + $this->assertEquals($expected, $generatedJson); + } + +} diff --git a/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php b/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php new file mode 100644 index 0000000000..8cacb5c4e0 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php @@ -0,0 +1,109 @@ +composerJson(), $this->composerLock()); + } + + /** + * Data for a composer.json fixture. + * + * @return array + * composer.json fixture data. + */ + protected function composerJson() { + return [ + 'name' => 'drupal/project-fixture', + 'description' => 'A fixture for testing the metapackage generator.', + 'type' => 'project', + 'license' => 'GPL-2.0-or-later', + 'require' => + [ + 'php' => '>=7.0.8', + 'symfony/yaml' => '~3.4.5', + ], + 'require-dev' => + [ + 'behat/mink' => '1.7.x-dev', + ], + ]; + } + + /** + * Data for a composer.lock fixture. + * + * @return array + * composer.lock fixture data. + */ + protected function composerLock() { + return [ + '_readme' => + [ + 'This is a composer.lock fixture. It contains only a subset of a', + 'typical composer.lock file (just what is needed for testing).', + ], + 'content-hash' => 'da9910627bab73a256b39ceda83d7167', + 'packages' => + [ + [ + 'name' => 'symfony/polyfill-ctype', + 'version' => 'v1.12.0', + 'source' => + [ + 'type' => 'git', + 'url' => 'https://github.com/symfony/polyfill-ctype.git', + 'reference' => '550ebaac289296ce228a706d0867afc34687e3f4', + ], + ], + [ + 'name' => 'symfony/yaml', + 'version' => 'v3.4.32', + 'source' => + [ + 'type' => 'git', + 'url' => 'https://github.com/symfony/yaml.git', + 'reference' => '768f817446da74a776a31eea335540f9dcb53942', + ], + ], + ], + 'packages-dev' => + [ + [ + 'name' => 'behat/mink', + 'version' => 'dev-master', + 'source' => + [ + 'type' => 'git', + 'url' => 'https://github.com/minkphp/Mink.git', + 'reference' => 'a534fe7dac9525e8e10ca68e737c3d7e5058ec83', + ], + ], + [ + 'name' => 'symfony/css-selector', + 'version' => 'v4.3.5', + 'source' => + [ + 'type' => 'git', + 'url' => 'https://github.com/symfony/css-selector.git', + 'reference' => 'f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9', + ], + ], + ], + ]; + } + +} diff --git a/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php b/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php new file mode 100644 index 0000000000..aee83f0c68 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php @@ -0,0 +1,85 @@ +getPackage(); + $generatedJson = PackageGenerator::encode($generatedJson); + + // Also load the most-recently-generated version of the metapackage. + $loadedJson = file_get_contents("$repositoryRoot/$path/composer.json"); + + // The generated json is the "expected", what we think the loaded + // json would contain, if the current patch is generated correctly + // (metapackages updated when composer.lock is updated). + $version = str_replace('.0-dev', '.x-dev', \Drupal::VERSION); + $message = <<< __EOT__ +The rebuilt version of $path does not match what is in the source tree. + +To fix, run: + + COMPOSER_ROOT_VERSION=$version composer update --lock + +__EOT__; + $this->assertEquals($generatedJson, $loadedJson, $message); + } + +} diff --git a/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock b/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock new file mode 100644 index 0000000000..bcf6d8a303 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock @@ -0,0 +1,17 @@ +{ + "_readme": [ + "This file is a fixture used to test Drupal." + ], + "content-hash": "da9910627bab73a256b39ceda83d7167", + "packages-dev": [ + { + "name": "behat/mink-selenium2-driver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkSelenium2Driver.git", + "reference": "0a09c4341621fca937a726827611b20ce3e2c259" + } + } + ] +} diff --git a/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php b/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php deleted file mode 100644 index 906b45d4e5..0000000000 --- a/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertNull(Composer::ensureComposerVersion()); - } - catch (\RuntimeException $e) { - $this->assertRegExp('/Drupal core development requires Composer 1.9.0, but Composer /', $e->getMessage()); - } - } - -}