diff --git a/composer.json b/composer.json index 3f2507aae9b94c71a3a83b6e4a02f4df5f493dd5..2253ad888a1310bf1f4640ae4b6e396523230afd 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,6 @@ "ext-json": "*", "drupal/core": "^9.7 || ^10", "php-tuf/composer-stager": "^1.2", - "composer/composer": "^2.2.12 || ^2.3.5", "composer-runtime-api": "^2.1" }, "scripts": { diff --git a/package_manager/core_packages.yml b/package_manager/core_packages.yml index d1b29edfd196e694ef64739a2f6818a3d42d87ea..d3b8151a19a45e71c40395c7f9033d9d04efdb41 100644 --- a/package_manager/core_packages.yml +++ b/package_manager/core_packages.yml @@ -1,4 +1,4 @@ -# This file exists so that \Drupal\package_manager\ComposerUtility can discern +# This file exists so that \Drupal\package_manager\InstalledPackagesList knows # which installed packages are considered part of Drupal core. There's no way # to tell by package type alone, since these packages have varying types, but # are all part of Drupal core's repository. This file is for internal use and diff --git a/package_manager/package_manager.api.php b/package_manager/package_manager.api.php index 533231663c1b100cc4cef883929abc5578d2bd55..c50ac7917eab238300a27dc2f90e58a26650bab7 100644 --- a/package_manager/package_manager.api.php +++ b/package_manager/package_manager.api.php @@ -144,14 +144,6 @@ * Destroys the stage directory, releases ownership, and dispatches pre- and * post-destroy events. * - * - \Drupal\package_manager\Stage::getActiveComposer() - * \Drupal\package_manager\Stage::getStageComposer() - * These methods initialize an instance of Composer's API in the active - * directory and stage directory, respectively, and return an object which - * can be used by event subscribers to inspect the directory and get relevant - * information from Composer's API, such as what packages are installed and - * where. - * * If problems occur during any point of the stage life cycle, a * \Drupal\package_manager\Exception\StageException is thrown. If problems were * detected during one of the "pre" operations, a subclass of that is thrown: @@ -207,6 +199,9 @@ * To be able to do enforce those constraints, these event subscribers need to * know where to look: \Drupal\package_manager\PathLocator informs them where * the project root, the vendor directory, et cetera are. + * If the constraints involve Composer aspects (such as installed packages, + * required configuration …), then \Drupal\package_manager\ComposerInspector is + * available as a service. * Whenever a problem is encountered, an event subscriber should generate one or * more messages (with a summary if there's multiple) to explain it to the user * and call \Drupal\package_manager\Event\PreOperationStageEvent::addError() or diff --git a/package_manager/src/ComposerInspector.php b/package_manager/src/ComposerInspector.php index 35ff2339554c01bccffa549d53bf6a05363eb8dc..c0a086194900db977163be9ed10c818bac673e44 100644 --- a/package_manager/src/ComposerInspector.php +++ b/package_manager/src/ComposerInspector.php @@ -16,10 +16,11 @@ use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactoryInterface; /** * Defines a class to get information from Composer. * - * @internal - * This is an internal part of Package Manager and may be changed or removed - * at any time without warning. External code should not interact with this - * class. + * This is a PHP wrapper to facilitate interacting with composer and: + * - list installed packages: getInstalledPackagesList() (`composer show`) + * - validate composer state & project: validate() (`composer validate`) + * - read project & package configuration: getConfig() (`composer config`) + * - read root package info: getRootPackageInfo() (`composer show --self`) */ class ComposerInspector { diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php deleted file mode 100644 index d85ab5ecb526d22ce2289d8ed79b0310d6b9b6c6..0000000000000000000000000000000000000000 --- a/package_manager/src/ComposerUtility.php +++ /dev/null @@ -1,373 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\package_manager; - -use Composer\Composer; -use Composer\Factory; -use Composer\IO\NullIO; -use Composer\Package\Loader\ValidatingArrayLoader; -use Composer\Package\PackageInterface; -use Composer\Package\Version\VersionParser; -use Composer\PartialComposer; -use Composer\Semver\Comparator; -use Drupal\Component\Serialization\Yaml; - -/** - * Defines a utility object to get information from Composer's API. - */ -class ComposerUtility { - - /** - * The Composer instance. - * - * @var \Composer\Composer - */ - protected $composer; - - /** - * The statically cached names of the Drupal core packages. - * - * @var string[] - */ - private static $corePackages; - - /** - * Whether to raise a deprecation error when the constructor is called. - * - * @var bool - */ - private static $triggerConstructorDeprecation = TRUE; - - /** - * Constructs a new ComposerUtility object. - * - * @param \Composer\Composer|\Composer\PartialComposer $composer - * The Composer instance. - */ - public function __construct(object $composer) { - // @todo Remove this in https://www.drupal.org/project/automatic_updates/issues/3321474. - if (self::$triggerConstructorDeprecation) { - @trigger_error(__METHOD__ . '() is deprecated in automatic_updates:8.x-2.5 and will be removed in automatic_updates:3.0.0. Use ' . __CLASS__ . '::createForDirectory() instead. See https://www.drupal.org/node/3314137.', E_USER_DEPRECATED); - } - self::$triggerConstructorDeprecation = TRUE; - - // @todo Remove this check when either: - // - PHP 8 or later is required, in which case the $composer type hint - // should be Composer|PartialComposer. - // - Composer 2.3 or later is required, in which case the $composer type - // hint should be changed to PartialComposer. - // @todo Update in https://www.drupal.org/project/automatic_updates/issues/3321474 - // @todo Update in https://www.drupal.org/project/automatic_updates/issues/3321476 - if (!$composer instanceof Composer && !$composer instanceof PartialComposer) { - throw new \InvalidArgumentException('The $composer argument must be an instance of ' . Composer::class . ' or ' . PartialComposer::class); - } - $this->composer = $composer; - } - - /** - * Returns the underlying Composer instance. - * - * @return \Composer\Composer|\Composer\PartialComposer - * The Composer instance. - */ - public function getComposer(): object { - return $this->composer; - } - - /** - * Creates an instance of this class using the files in a given directory. - * - * @param string $dir - * The directory that contains composer.json and composer.lock. - * - * @return \Drupal\package_manager\ComposerUtility - * The utility object. - * - * @throws \InvalidArgumentException - * When $dir does not contain a composer.json file. - */ - public static function createForDirectory(string $dir): self { - $io = new NullIO(); - $configuration = $dir . DIRECTORY_SEPARATOR . 'composer.json'; - - // The Composer factory requires that either the HOME or COMPOSER_HOME - // environment variables be set, so momentarily set the COMPOSER_HOME - // variable to the directory we're trying to create a Composer instance for. - // We have to do this because the Composer factory doesn't give us a way to - // pass the home directory in. - // @see \Composer\Factory::getHomeDir() - $home = getenv('COMPOSER_HOME'); - // Disable the automatic generation of .htaccess files in the Composer home - // directory, since we are temporarily overriding that directory. - // @see \Composer\Factory::createConfig() - // @see https://getcomposer.org/doc/06-config.md#htaccess-protect - $htaccess = getenv('COMPOSER_HTACCESS_PROTECT'); - - $factory = new Factory(); - putenv("COMPOSER_HOME=$dir"); - putenv("COMPOSER_HTACCESS_PROTECT=false"); - // Initialize the Composer API with plugins disabled and only the root - // package loaded (i.e., nothing from the global Composer project will be - // considered or loaded). This allows us to inspect the project directory - // using Composer's API in a "hands-off" manner. - $composer = $factory->createComposer($io, $configuration, TRUE, $dir, FALSE); - putenv("COMPOSER_HOME=$home"); - putenv("COMPOSER_HTACCESS_PROTECT=$htaccess"); - - self::$triggerConstructorDeprecation = FALSE; - return new static($composer); - } - - /** - * Returns the canonical names of the supported core packages. - * - * @return string[] - * The canonical list of supported core package names, as listed in - * ../core_packages.json. - */ - protected static function getCorePackageList(): array { - if (self::$corePackages === NULL) { - $file = __DIR__ . '/../core_packages.yml'; - assert(file_exists($file), "$file does not exist."); - - $core_packages = file_get_contents($file); - $core_packages = Yaml::decode($core_packages); - - assert(is_array($core_packages), "$file did not contain a list of core packages."); - self::$corePackages = $core_packages; - } - return self::$corePackages; - } - - /** - * Returns the installed core packages. - * - * All packages listed in ../core_packages.json are considered core packages. - * - * @return \Composer\Package\PackageInterface[] - * The installed core packages. - */ - public function getCorePackages(): array { - $core_packages = array_intersect_key( - $this->getInstalledPackages(), - array_flip(static::getCorePackageList()) - ); - - // If drupal/core-recommended is present, it supersedes drupal/core, since - // drupal/core will always be one of its direct dependencies. - if (array_key_exists('drupal/core-recommended', $core_packages)) { - unset($core_packages['drupal/core']); - } - return $core_packages; - } - - /** - * Returns information on all installed packages. - * - * @return \Composer\Package\PackageInterface[] - * All installed packages, keyed by name. - */ - public function getInstalledPackages(): array { - $installed_packages = $this->getComposer() - ->getRepositoryManager() - ->getLocalRepository() - ->getPackages(); - - $packages = []; - foreach ($installed_packages as $package) { - $key = $package->getName(); - $packages[$key] = $package; - } - return $packages; - } - - /** - * Returns the packages that are in the current project, but not in another. - * - * @param self $other - * A Composer utility wrapper around a different directory. - * - * @return \Composer\Package\PackageInterface[] - * The packages which are installed in the current project, but not in the - * other one, keyed by name. - */ - public function getPackagesNotIn(self $other): array { - return array_diff_key($this->getInstalledPackages(), $other->getInstalledPackages()); - } - - /** - * Returns the packages which have a different version in another project. - * - * This compares the current project with another one, and returns packages - * which are present in both, but in different versions. - * - * @param self $other - * A Composer utility wrapper around a different directory. - * - * @return \Composer\Package\PackageInterface[] - * The packages which are present in both the current project and the other - * one, but in different versions, keyed by name. - */ - public function getPackagesWithDifferentVersionsIn(self $other): array { - $theirs = $other->getInstalledPackages(); - - // Only compare packages that are both here and there. - $packages = array_intersect_key($this->getInstalledPackages(), $theirs); - - $filter = function (PackageInterface $package, string $name) use ($theirs): bool { - return Comparator::notEqualTo($package->getVersion(), $theirs[$name]->getVersion()); - }; - return array_filter($packages, $filter, ARRAY_FILTER_USE_BOTH); - } - - /** - * Returns installed package data from Composer's `installed.php`. - * - * @return array - * The installed package data as represented in Composer's `installed.php`, - * keyed by package name. - */ - public function getInstalledPackagesData(): array { - $installed_php = implode(DIRECTORY_SEPARATOR, [ - // Composer returns the absolute path to the vendor directory by default. - $this->getComposer()->getConfig()->get('vendor-dir'), - 'composer', - 'installed.php', - ]); - $data = include $installed_php; - return $data['versions']; - } - - /** - * Returns the Drupal project name for a given Composer package. - * - * @param string $package_name - * The name of the package. - * - * @return string|null - * The name of the Drupal project installed by the package, or NULL if: - * - The package is not installed. - * - The package is not of a supported type (one of `drupal-module`, - * `drupal-theme`, or `drupal-profile`). - * - The package name does not begin with `drupal/`. - * - The project name could not otherwise be determined. - */ - public function getProjectForPackage(string $package_name): ?string { - $data = $this->getInstalledPackagesData(); - - if (array_key_exists($package_name, $data)) { - $package = $data[$package_name]; - - $supported_package_types = [ - 'drupal-module', - 'drupal-theme', - 'drupal-profile', - ]; - // Only consider packages which are packaged by drupal.org and will be - // known to the core Update module. - if (str_starts_with($package_name, 'drupal/') && in_array($package['type'], $supported_package_types, TRUE)) { - return $this->scanForProjectName($package['install_path']); - } - } - return NULL; - } - - /** - * Returns the package name for a given Drupal project. - * - * @param string $project_name - * The name of the project. - * - * @return string|null - * The name of the Composer package which installs the project, or NULL if - * it could not be determined. - */ - public function getPackageForProject(string $project_name): ?string { - $installed = $this->getInstalledPackagesData(); - - $installed = array_keys($installed); - foreach ($installed as $package_name) { - if ($this->getProjectForPackage($package_name) === $project_name) { - return $package_name; - } - } - return NULL; - } - - /** - * Determines whether a Composer requirement string is valid. - * - * @param string $requirement - * A requirement string, optionally with a version constraint, e.g., - * "vendor/package" or "vendor/package:1.2.3", or any combination of package - * name and requirement string that Composer understands. - * - * @return bool - * TRUE if the requirement string is valid, FALSE otherwise. - * - * @see https://getcomposer.org/doc/04-schema.md#name - * @see https://getcomposer.org/doc/articles/versions.md - * - * @internal - * This method may be changed or removed at any time without warning and - * should not be used by external code. - */ - public static function isValidRequirement(string $requirement): bool { - $version_parser = new VersionParser(); - $parts = $version_parser->parseNameVersionPairs([$requirement])[0]; - $package_name = $parts['name']; - $version = $parts['version'] ?? NULL; - - // Validate just the package name. - if (ValidatingArrayLoader::hasPackageNamingError($package_name)) { - return FALSE; - } - - // Return early if there's no version constraint to validate. - if ($version === NULL) { - return TRUE; - } - - // Validate the version constraint. - try { - $version_parser->parseConstraints($version); - } - catch (\UnexpectedValueException) { - return FALSE; - } - - // All good. - return TRUE; - } - - /** - * Scans a given path to determine the Drupal project name. - * - * The path will be scanned for `.info.yml` files containing a `project` key. - * - * @param string $path - * The path to scan. - * - * @return string|null - * The name of the project, as declared in the first found `.info.yml` which - * contains a `project` key, or NULL if none was found. - */ - private function scanForProjectName(string $path): ?string { - $iterator = new \RecursiveDirectoryIterator($path); - $iterator = new \RecursiveIteratorIterator($iterator); - $iterator = new \RegexIterator($iterator, '/.+\.info\.yml$/', \RecursiveRegexIterator::GET_MATCH); - - foreach ($iterator as $match) { - $info = file_get_contents($match[0]); - $info = Yaml::decode($info); - - if (is_string($info['project']) && !empty($info['project'])) { - return $info['project']; - } - } - return NULL; - } - -} diff --git a/package_manager/src/InstalledPackagesList.php b/package_manager/src/InstalledPackagesList.php index 70dd5adb559b38595c511ffb00bce6154f1318dd..aba5d654b03610958920e837b906c15ac41b0e01 100644 --- a/package_manager/src/InstalledPackagesList.php +++ b/package_manager/src/InstalledPackagesList.php @@ -98,7 +98,7 @@ final class InstalledPackagesList extends \ArrayObject { } /** - * Returns the package name for a given Drupal project. + * Returns the package for a given Drupal project name, if it is installed. * * Although it is common for the package name to match the project name (for * example, a project name of `token` is likely part of the `drupal/token` diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php index 8d1fb28afbf705f6ef50ed8bb28ad97782826f2f..401bc31f00afe56122ef7f030c7cb6f09a4ad6a5 100644 --- a/package_manager/src/Stage.php +++ b/package_manager/src/Stage.php @@ -562,28 +562,6 @@ class Stage implements LoggerAwareInterface { } } - /** - * Returns a Composer utility object for the active directory. - * - * @return \Drupal\package_manager\ComposerUtility - * The Composer utility object. - */ - public function getActiveComposer(): ComposerUtility { - $dir = $this->pathLocator->getProjectRoot(); - return ComposerUtility::createForDirectory($dir); - } - - /** - * Returns a Composer utility object for the stage directory. - * - * @return \Drupal\package_manager\ComposerUtility - * The Composer utility object. - */ - public function getStageComposer(): ComposerUtility { - $dir = $this->getStageDirectory(); - return ComposerUtility::createForDirectory($dir); - } - /** * Attempts to claim the stage. * @@ -758,9 +736,7 @@ class Stage implements LoggerAwareInterface { * to ::require(). * * @throws \InvalidArgumentException - * Thrown if any of the given package names are invalid. - * - * @see https://getcomposer.org/doc/articles/composer-platform-dependencies.md + * Thrown if any of the given package names fail basic validation. */ protected static function validateRequirements(array $requirements): void { $version_parser = new VersionParser(); diff --git a/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.json b/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.json deleted file mode 100644 index 4c9f037cd98f293bec9cab46745bb776f0324a26..0000000000000000000000000000000000000000 --- a/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "packages": [ - { - "name": "the-org/the-package", - "version": "9.8.0" - } - ], - "dev-package-names": [] -} \ No newline at end of file diff --git a/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.php b/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.php deleted file mode 100644 index e6e509a25db6f5aad8a9962d484c09db0fa96190..0000000000000000000000000000000000000000 --- a/package_manager/tests/fixtures/FixtureUtilityTraitTest/missing_installed_php/vendor/composer/installed.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -/** - * @file - * Fixture for \Drupal\Tests\package_manager\Kernel\FixtureUtilityTraitTest::testModifyPackage(). - */ - -// Composer Utility needs the versions key to be present. -return [ - 'versions' => [], -]; diff --git a/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php b/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php index 5e4d7f7719687318b62903a2f98d1cf2152c4086..a38e4771dedc80209a42cb0b878a61f3ae2353aa 100644 --- a/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php +++ b/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php @@ -237,7 +237,7 @@ class FixtureManipulator { return $this; } - $output = $this->runComposerCommand(array_filter(['remove', $name, $is_dev_requirement ? '--dev' : NULL, '--no-update'])); + $output = $this->runComposerCommand(array_filter(['remove', $name, $is_dev_requirement ? '--dev' : NULL])); // `composer remove` will not set exit code 1 whenever a non-required // package is being removed. // @see \Composer\Command\RemoveCommand @@ -245,10 +245,6 @@ class FixtureManipulator { $output->stderr = str_replace("./composer.json has been updated\n", '', $output->stderr); throw new \LogicException($output->stderr); } - - // Make sure that `installed.json` & `installed.php` are updated. - // @todo Remove this when ComposerUtility gets removed. - $this->runComposerCommand(['update', $name]); return $this; } @@ -624,15 +620,6 @@ class FixtureManipulator { * Sets up the path repos at absolute paths. */ public function setUpRepos(): void { - // Some of the test coverage for FixtureManipulator tests edge cases for - // making installed.php invalid, and those test fixtures do NOT have a - // composer.json because ComposerUtility didn't look at that! - // @todo Remove this early return when ComposerUtility gets removed along - // with that edge case test coverage. - // @see fixtures/FixtureUtilityTraitTest/missing_installed_php - if (!file_exists($this->dir . '/composer.json')) { - return; - } $fs = new SymfonyFileSystem(); $path_repo_base = \Drupal::state()->get(self::PATH_REPO_STATE_KEY); if (empty($path_repo_base)) { diff --git a/package_manager/tests/src/Build/TemplateProjectTestBase.php b/package_manager/tests/src/Build/TemplateProjectTestBase.php index 597c097384708100972f3f40c8305dfb4ec5cf0a..4b6195b7097bc7f11fc4e0bc62254f036e557f9c 100644 --- a/package_manager/tests/src/Build/TemplateProjectTestBase.php +++ b/package_manager/tests/src/Build/TemplateProjectTestBase.php @@ -256,7 +256,7 @@ END; // If using the drupal/recommended-project template, we don't expect there // to be an .htaccess file at the project root. One would normally be // generated by Composer when Package Manager or other code creates a - // ComposerUtility object in the active directory, except that Package + // ComposerInspector object in the active directory, except that Package // Manager takes specific steps to prevent that. So, here we're just // confirming that, in fact, Composer's .htaccess protection was disabled. // We don't do this for the drupal/legacy-project template because its diff --git a/package_manager/tests/src/Kernel/ComposerUtilityTest.php b/package_manager/tests/src/Kernel/ComposerUtilityTest.php deleted file mode 100644 index 9f7a1f9945e98f99f0ee215f622aff5c955fa812..0000000000000000000000000000000000000000 --- a/package_manager/tests/src/Kernel/ComposerUtilityTest.php +++ /dev/null @@ -1,249 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\Tests\package_manager\Kernel; - -use Drupal\Component\FileSystem\FileSystem as DrupalFileSystem; -use Drupal\Core\Serialization\Yaml; -use Drupal\fixture_manipulator\FixtureManipulator; -use Drupal\KernelTests\KernelTestBase; -use Drupal\package_manager\ComposerUtility; -use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait; -use Drupal\Tests\package_manager\Traits\ComposerInstallersTrait; -use Drupal\Tests\package_manager\Traits\FixtureUtilityTrait; -use Symfony\Component\Filesystem\Filesystem; - -/** - * @coversDefaultClass \Drupal\package_manager\ComposerUtility - * @group package_manager - * @internal - */ -class ComposerUtilityTest extends KernelTestBase { - - use AssertPreconditionsTrait; - use ComposerInstallersTrait; - use FixtureUtilityTrait; - - /** - * The temporary root directory for testing. - * - * @var string - */ - protected string $rootDir; - - /** - * {@inheritdoc} - */ - protected static $modules = ['package_manager', 'update']; - - /** - * {@inheritdoc} - */ - protected function setUp(): void { - parent::setUp(); - - $this->rootDir = DrupalFileSystem::getOsTemporaryDirectory() . DIRECTORY_SEPARATOR . 'composer_utility_testing_root' . $this->databasePrefix; - $fs = new Filesystem(); - if (is_dir($this->rootDir)) { - $fs->remove($this->rootDir); - } - $fs->mkdir($this->rootDir); - $fixture = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture' . DIRECTORY_SEPARATOR; - static::copyFixtureFilesTo(__DIR__ . '/../../fixtures/fake_site', $fixture); - $this->installComposerInstallers($fixture); - $projects_dir = 'web/projects'; - $manipulator = new FixtureManipulator(); - $manipulator->addPackage( - [ - 'name' => 'drupal/package_project_match', - 'type' => 'drupal-module', - ], - FALSE, - TRUE - ); - $installer_paths["$projects_dir/package_project_match"] = ['drupal/package_project_match']; - - $manipulator->addPackage( - [ - 'name' => 'drupal/not_match_package', - 'type' => 'drupal-module', - ], - FALSE, - TRUE, - // Create an info.yml file with a different project name from the - // package. - ['not_match_project.info.yml' => Yaml::encode(['project' => 'not_match_project'])], - ); - $installer_paths["$projects_dir/not_match_project"] = ['drupal/not_match_package']; - $manipulator->addPackage( - [ - 'name' => 'drupal/not_match_path_project', - 'type' => 'drupal-module', - ], - FALSE, - TRUE, - [] - ); - $installer_paths["$projects_dir/not_match_path_project"] = ['drupal/not_match_path_project']; - $manipulator->addPackage( - [ - 'name' => 'drupal/nested_no_match_package', - 'type' => 'drupal-module', - ], - FALSE, - TRUE, - // A test info.yml file where the folder names and info.yml file names - // do not match the project or package. Only the project key in this - // file need to match. - ['any_sub_folder/any_yml_file.info.yml' => Yaml::encode(['project' => 'nested_no_match_project'])], - ); - $installer_paths["$projects_dir/any_folder_name"] = ['drupal/nested_no_match_package']; - $manipulator->addPackage( - [ - 'name' => 'non_drupal/other_project', - 'type' => 'drupal-module', - ], - FALSE, - TRUE - ); - $installer_paths["$projects_dir/other_project"] = ['non_drupal/other_project']; - $manipulator->addPackage( - [ - 'name' => 'drupal/custom_module', - 'type' => 'drupal-custom-module', - ], - FALSE, - TRUE - ); - $installer_paths["$projects_dir/custom_module"] = ['drupal/custom_module']; - - // Commit the changes to 'installer-paths' first so that all the packages - // will be installed at the correct paths. - $this->setInstallerPaths($installer_paths, $fixture); - $manipulator->commitChanges($fixture); - } - - /** - * Tests that ComposerUtility::CreateForDirectory() validates the directory. - */ - public function testCreateForDirectoryValidation(): void { - $dir = $this->rootDir; - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Composer could not find the config file: ' . $dir . DIRECTORY_SEPARATOR . 'composer.json'); - - ComposerUtility::createForDirectory($dir); - } - - /** - * Tests that ComposerUtility disables automatic creation of .htaccess files. - */ - public function testHtaccessProtectionDisabled(): void { - $dir = $this->rootDir; - file_put_contents($dir . '/composer.json', '{}'); - - ComposerUtility::createForDirectory($dir); - $this->assertFileDoesNotExist($dir . '/.htaccess'); - } - - /** - * @covers ::getProjectForPackage - * - * @param string $package - * The package name. - * @param string|null $expected_project - * The expected project if any, otherwise NULL. - * - * @dataProvider providerGetProjectForPackage - */ - public function testGetProjectForPackage(string $package, ?string $expected_project): void { - $dir = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture'; - $this->assertSame($expected_project, ComposerUtility::createForDirectory($dir)->getProjectForPackage($package)); - } - - /** - * Data provider for ::testGetProjectForPackage(). - * - * @return mixed[][] - * The test cases. - */ - public function providerGetProjectForPackage(): array { - return [ - 'package and project match' => [ - 'drupal/package_project_match', - 'package_project_match', - ], - 'package and project do not match' => [ - 'drupal/not_match_package', - 'not_match_project', - ], - 'vendor is not drupal' => [ - 'non_drupal/other_project', - NULL, - ], - 'missing package' => [ - 'drupal/missing', - NULL, - ], - 'nested_no_match' => [ - 'drupal/nested_no_match_package', - 'nested_no_match_project', - ], - 'unsupported package type' => [ - 'drupal/custom_module', - NULL, - ], - ]; - } - - /** - * @covers ::getPackageForProject - * - * @param string $project - * The project name. - * @param string|null $expected_package - * The expected package if any, otherwise NULL. - * - * @dataProvider providerGetPackageForProject - */ - public function testGetPackageForProject(string $project, ?string $expected_package): void { - $dir = $this->rootDir . DIRECTORY_SEPARATOR . 'fixture'; - $this->assertSame($expected_package, ComposerUtility::createForDirectory($dir)->getPackageForProject($project)); - } - - /** - * Data provider for ::testGetPackageForProject(). - * - * @return mixed[][] - * The test cases. - */ - public function providerGetPackageForProject(): array { - return [ - 'package and project match' => [ - 'package_project_match', - 'drupal/package_project_match', - ], - 'package and project do not match' => [ - 'not_match_project', - 'drupal/not_match_package', - ], - 'package and project match + wrong installed path' => [ - 'not_match_path_project', - NULL, - ], - 'vendor is not drupal' => [ - 'other_project', - NULL, - ], - 'missing package' => [ - 'missing', - NULL, - ], - 'nested_no_match' => [ - 'nested_no_match_project', - 'drupal/nested_no_match_package', - ], - ]; - } - -} diff --git a/package_manager/tests/src/Kernel/FakeSiteFixtureTest.php b/package_manager/tests/src/Kernel/FakeSiteFixtureTest.php index 7d742a8d62e78d947670809fbcdf6a7d4fdf6ca6..f48efba8203a06fbe339b0f315f4b7fef4624f62 100644 --- a/package_manager/tests/src/Kernel/FakeSiteFixtureTest.php +++ b/package_manager/tests/src/Kernel/FakeSiteFixtureTest.php @@ -5,7 +5,6 @@ declare(strict_types = 1); namespace Drupal\Tests\package_manager\Kernel; use Drupal\fixture_manipulator\ActiveFixtureManipulator; -use Drupal\package_manager\ComposerUtility; use Symfony\Component\Process\Process; /** @@ -28,18 +27,15 @@ class FakeSiteFixtureTest extends PackageManagerKernelTestBase { } /** - * Tests calls to ComposerUtility class methods. + * Tests calls to ComposerInspector class methods. */ - public function testCallToComposerUtilityMethods(): void { + public function testCallToComposerInspectorMethods(): void { $active_dir = $this->container->get('package_manager.path_locator')->getProjectRoot(); - $composer_utility = ComposerUtility::createForDirectory($active_dir); - // Although the fake-site fixture does not contain any Composer packages or - // Drupal projects that would be returned from these methods calling them - // and asserting that they return NULL proves there are not any missing - // metadata in the fixture files that would cause these methods to throw an - // exception. - $this->assertNull($composer_utility->getProjectForPackage('any_random_name')); - $this->assertNull($composer_utility->getPackageForProject('drupal/any_random_name')); + /** @var \Drupal\package_manager\ComposerInspector $inspector */ + $inspector = $this->container->get('package_manager.composer_inspector'); + $list = $inspector->getInstalledPackagesList($active_dir); + $this->assertNull($list->getPackageByDrupalProjectName('any_random_name')); + $this->assertFalse(isset($list['drupal/any_random_name'])); } /** diff --git a/package_manager/tests/src/Unit/ComposerUtilityTest.php b/package_manager/tests/src/Unit/ComposerUtilityTest.php deleted file mode 100644 index 7a004680bd175f92633848b0f0e5815699f6496d..0000000000000000000000000000000000000000 --- a/package_manager/tests/src/Unit/ComposerUtilityTest.php +++ /dev/null @@ -1,207 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\Tests\package_manager\Unit; - -use Composer\Package\PackageInterface; -use Drupal\package_manager\ComposerUtility; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\package_manager\ComposerUtility - * @group package_manager - * @internal - */ -class ComposerUtilityTest extends UnitTestCase { - - /** - * Data provider for ::testCorePackages(). - * - * @return \string[][][] - * The test cases. - */ - public function providerCorePackages(): array { - return [ - 'core-recommended not installed' => [ - ['drupal/core'], - ['drupal/core'], - ], - 'core-recommended installed' => [ - ['drupal/core', 'drupal/core-recommended'], - ['drupal/core-recommended'], - ], - ]; - } - - /** - * @covers ::getCorePackages - * - * @param string[] $installed_package_names - * The names of the packages that are installed. - * @param string[] $expected_core_package_names - * The expected core package names that should be returned by - * ::getCorePackages(). - * - * @dataProvider providerCorePackages - */ - public function testCorePackages(array $installed_package_names, array $expected_core_package_names): void { - $versions = array_fill(0, count($installed_package_names), '1.0.0'); - $installed_packages = array_combine($installed_package_names, $versions); - - $core_packages = $this->mockUtilityWithPackages($installed_packages) - ->getCorePackages(); - $this->assertSame($expected_core_package_names, array_keys($core_packages)); - } - - /** - * @covers ::isValidRequirement - * - * @param bool $expected_is_valid - * Whether the given requirement string is valid. - * @param string $requirement - * The requirement string to validate. - * - * @dataProvider providerIsValidRequirement - */ - public function testIsValidRequirement(bool $expected_is_valid, string $requirement): void { - $this->assertSame($expected_is_valid, ComposerUtility::isValidRequirement($requirement)); - } - - /** - * Data provider for ::testIsValidRequirement(). - * - * @return \string[][][] - * The test cases. - */ - public function providerIsValidRequirement(): array { - return [ - // Valid requirements. - [TRUE, 'vendor/package'], - [TRUE, 'vendor/snake_case'], - [TRUE, 'vendor/kebab-case'], - [TRUE, 'vendor/with.dots'], - [TRUE, '1vendor2/3package4'], - [TRUE, 'vendor/package:1'], - [TRUE, 'vendor/package:1.2'], - [TRUE, 'vendor/package:1.2.3'], - [TRUE, 'vendor/package:1.x'], - [TRUE, 'vendor/package:^1'], - [TRUE, 'vendor/package:~1'], - [TRUE, 'vendor/package:>1'], - [TRUE, 'vendor/package:<1'], - [TRUE, 'vendor/package:>=1'], - [TRUE, 'vendor/package:>1 <2'], - [TRUE, 'vendor/package:1 || 2'], - [TRUE, 'vendor/package:>=1,<1.1.0'], - [TRUE, 'vendor/package:1a'], - [TRUE, 'vendor/package:*'], - [TRUE, 'vendor/package:dev-master'], - [TRUE, 'vendor/package:*@dev'], - [TRUE, 'vendor/package:@dev'], - [TRUE, 'vendor/package:master@dev'], - [TRUE, 'vendor/package:master@beta'], - [TRUE, 'php'], - [TRUE, 'php:8'], - [TRUE, 'php:8.0'], - [TRUE, 'php:^8.1'], - [TRUE, 'php:~8.1'], - [TRUE, 'php-64bit'], - [TRUE, 'composer'], - [TRUE, 'composer-plugin-api'], - [TRUE, 'composer-plugin-api:1'], - [TRUE, 'ext-json'], - [TRUE, 'ext-json:1'], - [TRUE, 'ext-pdo_mysql'], - [TRUE, 'ext-pdo_mysql:1'], - [TRUE, 'lib-curl'], - [TRUE, 'lib-curl:1'], - [TRUE, 'lib-curl-zlib'], - [TRUE, 'lib-curl-zlib:1'], - - // Invalid requirements. - [FALSE, ''], - [FALSE, ' '], - [FALSE, '/'], - [FALSE, 'php8'], - [FALSE, 'package'], - [FALSE, 'vendor\package'], - [FALSE, 'vendor//package'], - [FALSE, 'vendor/package1 vendor/package2'], - [FALSE, 'vendor/package/extra'], - [FALSE, 'vendor/package:a'], - [FALSE, 'vendor/package:'], - [FALSE, 'vendor/package::'], - [FALSE, 'vendor/package::1'], - [FALSE, 'vendor/package:1:2'], - [FALSE, 'vendor/package:develop@dev@dev'], - [FALSE, 'vendor/package:develop@'], - [FALSE, 'vEnDor/pAcKaGe'], - [FALSE, '_vendor/package'], - [FALSE, '_vendor/_package'], - [FALSE, 'vendor_/package'], - [FALSE, '_vendor/package_'], - [FALSE, 'vendor/package-'], - [FALSE, 'php-'], - [FALSE, 'ext'], - [FALSE, 'lib'], - ]; - } - - /** - * @covers ::getPackagesNotIn - * @covers ::getPackagesWithDifferentVersionsIn - */ - public function testPackageComparison(): void { - $active = $this->mockUtilityWithPackages([ - 'drupal/existing' => '1.0.0', - 'drupal/updated' => '1.0.0', - 'drupal/removed' => '1.0.0', - ]); - $staged = $this->mockUtilityWithPackages([ - 'drupal/existing' => '1.0.0', - 'drupal/updated' => '1.1.0', - 'drupal/added' => '1.0.0', - ]); - - $added = $staged->getPackagesNotIn($active); - $this->assertSame(['drupal/added'], array_keys($added)); - - $removed = $active->getPackagesNotIn($staged); - $this->assertSame(['drupal/removed'], array_keys($removed)); - - $updated = $active->getPackagesWithDifferentVersionsIn($staged); - $this->assertSame(['drupal/updated'], array_keys($updated)); - } - - /** - * Mocks a ComposerUtility object to return a set of installed packages. - * - * @param string[]|null[] $installed_packages - * The installed packages that the mocked object should return. The keys are - * the package names and the values are either a version number or NULL to - * not mock the corresponding package's getVersion() method. - * - * @return \Drupal\package_manager\ComposerUtility|\PHPUnit\Framework\MockObject\MockObject - * The mocked object. - */ - private function mockUtilityWithPackages(array $installed_packages) { - $mock = $this->getMockBuilder(ComposerUtility::class) - ->disableOriginalConstructor() - ->onlyMethods(['getInstalledPackages']) - ->getMock(); - - $packages = []; - foreach ($installed_packages as $name => $version) { - $package = $this->createMock(PackageInterface::class); - if (isset($version)) { - $package->method('getVersion')->willReturn($version); - } - $packages[$name] = $package; - } - $mock->method('getInstalledPackages')->willReturn($packages); - - return $mock; - } - -} diff --git a/package_manager/tests/src/Unit/InstalledPackagesDataTest.php b/package_manager/tests/src/Unit/InstalledPackagesDataTest.php deleted file mode 100644 index a486184f27441693fb5a18ac8ee8dab4aa788e20..0000000000000000000000000000000000000000 --- a/package_manager/tests/src/Unit/InstalledPackagesDataTest.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\Tests\package_manager\Unit; - -use Composer\Autoload\ClassLoader; -use Drupal\Tests\UnitTestCase; - -/** - * Tests retrieval of package data from Composer's `installed.php`. - * - * ComposerUtility relies on the internal structure of `installed.php` for - * certain operations. This test is intended as an early warning if the file's - * internal structure changes in a way that would break our functionality. - * - * @todo Delete this test in https://drupal.org/i/3316368. - * - * @group package_manager - * @internal - */ -class InstalledPackagesDataTest extends UnitTestCase { - - /** - * Tests that Composer's `installed.php` file looks how we expect. - */ - public function testInstalledPackagesData(): void { - $loaders = ClassLoader::getRegisteredLoaders(); - $installed_php = key($loaders) . '/composer/installed.php'; - $this->assertFileIsReadable($installed_php); - $data = include $installed_php; - - // There should be a `versions` array whose keys are package names. - $this->assertIsArray($data['versions']); - $this->assertMatchesRegularExpression('|^[a-z0-9\-_]+/[a-z0-9\-_]+$|', key($data['versions'])); - - // The values of `versions` should be arrays of package information that - // includes a non-empty `install_path` string and a non-empty `type` string. - $package = reset($data['versions']); - $this->assertIsArray($package); - $this->assertNotEmpty($package['install_path']); - $this->assertIsString($package['install_path']); - $this->assertNotEmpty($package['type']); - $this->assertIsString($package['type']); - } - -} diff --git a/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php b/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php index b9fa93ee751622cc5c6ce2ec5e6ccaa28c05e9f6..c75800ac54d464709eba4c5996789225fab93948 100644 --- a/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/RequestedUpdateValidatorTest.php @@ -31,7 +31,7 @@ class RequestedUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { // requested version of '9.8.1'. This also does not update all packages that // are expected to be updated when updating Drupal core. // @see \Drupal\automatic_updates\Updater::begin() - // @see \Drupal\package_manager\ComposerUtility::getCorePackages() + // @see \Drupal\package_manager\InstalledPackagesList::getCorePackages() $this->getStageFixtureManipulator()->setVersion('drupal/core-recommended', '9.8.2'); $this->setCoreVersion('9.8.0'); $this->setReleaseMetadata([ @@ -64,7 +64,7 @@ class RequestedUpdateValidatorTest extends AutomaticUpdatesKernelTestBase { $this->getStageFixtureManipulator() ->removePackage('drupal/core') ->removePackage('drupal/core-recommended') - ->removePackage('drupal/core-dev'); + ->removePackage('drupal/core-dev', TRUE); $this->setCoreVersion('9.8.0'); $this->setReleaseMetadata([ diff --git a/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php b/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php index 0e81882e9161f37d99087cb6bcb1d976a36d0b08..bf92f74c33b34121dfa08fe794b676eaeb68ef0c 100644 --- a/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/StatusCheck/StagedProjectsValidatorTest.php @@ -132,7 +132,7 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { TRUE ) ->removePackage('other/removed') - ->removePackage('other/dev-removed'); + ->removePackage('other/dev-removed', TRUE); $messages = [ t("custom module 'drupal/dev-test-module2' installed."), @@ -204,7 +204,7 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { // The validator shouldn't complain about these packages being removed, // since it only cares about Drupal modules and themes. ->removePackage('other/removed') - ->removePackage('other/dev-removed') + ->removePackage('other/dev-removed', TRUE) ->setCorePackageVersion('9.8.1'); $messages = [ @@ -350,7 +350,7 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { ->setVersion('other/changed', '1.3.2') ->setVersion('other/dev-changed', '1.3.2') ->removePackage('other/removed') - ->removePackage('other/dev-removed'); + ->removePackage('other/dev-removed', TRUE); $updater = $this->container->get('automatic_updates.updater'); $updater->begin(['drupal' => '9.8.1']);