Skip to content
Snippets Groups Projects
Commit 7b6b9601 authored by Adam G-H's avatar Adam G-H
Browse files

Issue #3387637: The fake_site fixture should use a Composer-type repository

parent 653a22bd
No related branches found
No related tags found
No related merge requests found
Showing
with 188 additions and 123 deletions
......@@ -50,15 +50,11 @@ class ComposerInspector implements LoggerAwareInterface {
/**
* A semantic version constraint for the supported version(s) of Composer.
*
* Only versions supported by Composer are supported: the LTS and the latest
* minor version. Those are currently (May 2023) 2.2 and 2.5, but will be 2.5
* and 2.6 later this year (December 2023): the 2.2 LTS ends in December 2023.
*
* @see https://endoflife.date/composer
*
* @var string
*/
final public const SUPPORTED_VERSION = '~2.5.5 || ^2.6';
final public const SUPPORTED_VERSION = '^2.6';
/**
* Constructs a ComposerInspector object.
......
......@@ -23,39 +23,11 @@
"baz": null
},
"repositories": {
"packagist.org": false,
"drupal/core-recommended": {
"type": "path",
"version": "9.8.0",
"url": "../path_repos/drupal--core-recommended",
"options": {
"symlink": false
}
"fake-packages": {
"type": "composer",
"url": "./"
},
"drupal/core-dev": {
"type": "path",
"version": "9.8.0",
"url": "../path_repos/drupal--core-dev",
"options": {
"symlink": false
}
},
"drupal/core": {
"type": "path",
"version": "9.8.0",
"url": "../path_repos/drupal--core",
"options": {
"symlink": false
}
},
"cweagans/composer-patches": {
"type": "path",
"version": "1.7.333",
"url": "../path_repos/cweagans--composer-patches",
"options": {
"symlink": false
}
}
"packagist.org": false
},
"minimum-stability": "stable",
"config": {
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "672a2db4be3ba5e4f025ddea6b7dacd7",
"content-hash": "0235f8f2d3ef91d87864b76e12838e89",
"packages": [
{
"name": "drupal/core",
......@@ -50,11 +50,7 @@
}
}
},
"description": "A fake version of drupal/core",
"transport-options": {
"symlink": false,
"relative": true
}
"description": "A fake version of drupal/core"
},
{
"name": "drupal/core-recommended",
......@@ -65,11 +61,7 @@
"reference": "112e4f7cfe8312457cd0eb58dcbffebc148850d8"
},
"type": "project",
"description": "A fake version of drupal/core-recommended",
"transport-options": {
"symlink": false,
"relative": true
}
"description": "A fake version of drupal/core-recommended"
}
],
"packages-dev": [
......@@ -82,11 +74,7 @@
"reference": "b99a99a11ff2779b5e4c5787dc43575382a3548c"
},
"type": "package",
"description": "A fake version of drupal/core-dev",
"transport-options": {
"symlink": false,
"relative": true
}
"description": "A fake version of drupal/core-dev"
}
],
"aliases": [],
......@@ -96,5 +84,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}
{
"packages": {
"drupal/core-recommended": {
"9.8.0": {
"name": "drupal/core-recommended",
"description": "A fake version of drupal/core-recommended",
"type": "project",
"version": "9.8.0",
"dist": {
"type": "path",
"url": "../path_repos/drupal--core-recommended",
"reference": "112e4f7cfe8312457cd0eb58dcbffebc148850d8"
}
}
},
"drupal/core-dev": {
"9.8.0": {
"name": "drupal/core-dev",
"description": "A fake version of drupal/core-dev",
"type": "package",
"version": "9.8.0",
"dist": {
"type": "path",
"url": "../path_repos/drupal--core-dev",
"reference": "b99a99a11ff2779b5e4c5787dc43575382a3548c"
}
}
},
"drupal/core": {
"9.8.0": {
"name": "drupal/core",
"type": "drupal-core",
"description": "A fake version of drupal/core",
"version": "9.8.0",
"extra": {
"_readme": [
"The 'drupal-scaffold' section below is needed because 'Drupal\\automatic_updates\\Validator\\ScaffoldFilePermissionsValidator'",
"uses this section to determine which files to check. The actual composer.json file for drupal/core will have more files listed",
"but this limited list is used in '\\Drupal\\Tests\\automatic_updates\\Kernel\\StatusCheck\\ScaffoldFilePermissionsValidatorTest'",
"to ensure this section determines the file list."
],
"drupal-scaffold": {
"file-mapping": {
"[project-root]/.editorconfig": "assets/scaffold/files/editorconfig",
"[project-root]/.gitattributes": "assets/scaffold/files/gitattributes",
"[web-root]/.csslintrc": "assets/scaffold/files/csslintrc",
"[web-root]/.eslintignore": "assets/scaffold/files/eslintignore",
"[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json",
"[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php",
"[web-root]/.htaccess": "assets/scaffold/files/htaccess",
"[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore",
"[web-root]/index.php": "assets/scaffold/files/index.php",
"[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt",
"[web-root]/README.md": "assets/scaffold/files/drupal.README.md",
"[web-root]/robots.txt": "assets/scaffold/files/robots.txt",
"[web-root]/update.php": "assets/scaffold/files/update.php",
"[web-root]/web.config": "assets/scaffold/files/web.config",
"[web-root]/sites/README.txt": "assets/scaffold/files/sites.README.txt",
"[web-root]/sites/development.services.yml": "assets/scaffold/files/development.services.yml",
"[web-root]/sites/example.settings.local.php": "assets/scaffold/files/example.settings.local.php",
"[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php",
"[web-root]/sites/default/default.services.yml": "assets/scaffold/files/default.services.yml",
"[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php",
"[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt",
"[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt",
"[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt"
}
}
},
"dist": {
"type": "path",
"url": "../path_repos/drupal--core",
"reference": "31fd2270701526555acae45a3601c777e35508d5"
}
}
},
"cweagans/composer-patches": {
"1.7.333": {
"name": "cweagans/composer-patches",
"description": "A fake version of cweagans/composer-patches",
"type": "composer-plugin",
"version": "1.7.333",
"extra": {
"class": "\\cweagans\\Fake\\ComposerPatches"
},
"dist": {
"type": "path",
"url": "../path_repos/cweagans--composer-patches"
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"autoload": {
"psr-4": {
"cweagans\\Fake\\": "src"
}
}
}
}
}
}
......@@ -47,10 +47,6 @@
},
"installation-source": "dist",
"description": "A fake version of drupal/core",
"transport-options": {
"symlink": false,
"relative": true
},
"install-path": "../drupal/core"
},
{
......@@ -65,10 +61,6 @@
"type": "package",
"installation-source": "dist",
"description": "A fake version of drupal/core-dev",
"transport-options": {
"symlink": false,
"relative": true
},
"install-path": "../drupal/core-dev"
},
{
......@@ -83,10 +75,6 @@
"type": "project",
"installation-source": "dist",
"description": "A fake version of drupal/core-recommended",
"transport-options": {
"symlink": false,
"relative": true
},
"install-path": "../drupal/core-recommended"
}
],
......
......@@ -3,3 +3,5 @@ services:
autowire: true
Drupal\fixture_manipulator\StageFixtureManipulator:
decorates: 'PhpTuf\ComposerStager\API\Core\BeginnerInterface'
Drupal\fixture_manipulator\ProcessFactory:
decorates: 'PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface'
......@@ -377,18 +377,6 @@ class FixtureManipulator {
}
}
/**
* Ensures a path is writable.
*
* @param string $path
* The path.
*/
private static function ensureFilePathIsWritable(string $path): void {
if (!is_writable($path)) {
throw new \LogicException("'$path' is not writable.");
}
}
/**
* Creates an empty .git folder after being provided a path.
*/
......@@ -587,31 +575,18 @@ class FixtureManipulator {
$this->createPathRepo($package, $repo_path, NULL);
}
// Register the repository, keyed by package name and version. This ensures
// each package is registered only once and its version can be updated (but
// must have unique versions).
$repo_key = "repo.$name";
if ($is_new_or_fork === 'fork') {
$repositories = json_decode(file_get_contents($this->dir . '/composer.json'), TRUE, flags: JSON_THROW_ON_ERROR)['repositories'];
// @todo consistently use 'version' or 'options.versions.PACKAGE_NAME', by fixing ComposerFixtureCreator in https://drupal.org/i/3347055
$original_version = isset($repositories[$name]['version']) ? $repositories[$name]['version'] : $repositories[$name]['options']['versions'][$name];
if ($package['version'] !== $original_version) {
$repo_key .= "--" . $package['version'];
}
}
$repository = json_encode([
// Add the package to the Composer repository defined for the site.
$packages_json = $this->dir . '/packages.json';
$packages_data = file_get_contents($packages_json);
$packages_data = json_decode($packages_data, TRUE, flags: JSON_THROW_ON_ERROR);
$version = $package['version'];
$package['dist'] = [
'type' => 'path',
'url' => $repo_path,
'options' => [
'symlink' => FALSE,
'versions' => [
$name => $package['version'],
],
// @see https://getcomposer.org/repoprio
'canonical' => FALSE,
],
], JSON_UNESCAPED_SLASHES);
$this->runComposerCommand(['config', $repo_key, $repository]);
];
$packages_data['packages'][$name][$version] = $package;
file_put_contents($packages_json, json_encode($packages_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
return $repo_path;
}
......@@ -631,8 +606,8 @@ class FixtureManipulator {
}
// Update all the repos in the composer.json file to point to the new
// repos at the absolute path.
$composer_json = file_get_contents($this->dir . '/composer.json');
file_put_contents($this->dir . '/composer.json', str_replace('../path_repos/', "$path_repo_base/", $composer_json));
$composer_json = file_get_contents($this->dir . '/packages.json');
file_put_contents($this->dir . '/packages.json', str_replace('../path_repos/', "$path_repo_base/", $composer_json));
$this->runComposerCommand(['update', '--lock']);
$this->runComposerCommand(['install']);
}
......
<?php
namespace Drupal\fixture_manipulator;
use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
/**
* Process factory that always sets the COMPOSER_MIRROR_PATH_REPOS env variable.
*
* This is necessary because the fake_site fixture is built from a Composer-type
* repository, which will normally try to symlink packages which are installed
* from local directories, which in turn will break Package Manager because it
* does not support symlinks pointing outside the main code base. The
* COMPOSER_MIRROR_PATH_REPOS environment variable forces Composer to mirror,
* rather than symlink, local directories when installing packages.
*
* @see \Drupal\fixture_manipulator\FixtureManipulator::setUpRepos()
*/
final class ProcessFactory implements ProcessFactoryInterface {
/**
* Constructs a ProcessFactory object.
*
* @param \PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface $decorated
* The decorated process factory service.
*/
public function __construct(private readonly ProcessFactoryInterface $decorated) {}
/**
* {@inheritdoc}
*/
public function create(array $command): ProcessInterface {
$process = $this->decorated->create($command);
$env = $process->getEnv();
$env['COMPOSER_MIRROR_PATH_REPOS'] = '1';
return $process->setEnv($env);
}
}
......@@ -223,8 +223,8 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase {
* @testWith ["2.2.12", "<default>"]
* ["2.2.13", "<default>"]
* ["2.5.0", "<default>"]
* ["2.5.5", null]
* ["2.5.11", null]
* ["2.5.5", "<default>"]
* ["2.5.11", "<default>"]
* ["2.6.0", null]
* ["2.2.11", "<default>"]
* ["2.2.0-dev", "<default>"]
......
......@@ -145,10 +145,10 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
public function testModifyPackageConfig(): void {
// Assert ::modifyPackage() works with a package in an existing fixture not
// created by ::addPackage().
$decode_composer_json = function ($package_name): array {
return json_decode(file_get_contents($this->dir . "/vendor/$package_name/composer.json"), TRUE, flags: JSON_THROW_ON_ERROR);
$decode_packages_json = function (): array {
return json_decode(file_get_contents($this->dir . "/packages.json"), TRUE, flags: JSON_THROW_ON_ERROR);
};
$original_composer_json = $decode_composer_json('my/dev-package');
$original_packages_json = $decode_packages_json();
(new ActiveFixtureManipulator())
// @see ::setUp()
->modifyPackageConfig('my/dev-package', '2.1.0', ['description' => 'something else'], TRUE)
......@@ -156,10 +156,10 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
// Verify that the package is indeed properly installed.
$this->assertSame('2.1.0', $this->inspector->getInstalledPackagesList($this->dir)['my/dev-package']?->version);
// Verify that the original exists, but has no description.
$this->assertSame('my/dev-package', $original_composer_json['name']);
$this->assertArrayNotHasKey('description', $original_composer_json);
$this->assertSame('my/dev-package', $original_packages_json['packages']['my/dev-package']['2.1.0']['name']);
$this->assertArrayNotHasKey('description', $original_packages_json);
// Verify that the description was updated.
$this->assertSame('something else', $decode_composer_json('my/dev-package')['description']);
$this->assertSame('something else', $decode_packages_json()['packages']['my/dev-package']['2.1.0']['description']);
(new ActiveFixtureManipulator())
// Add a key to an existing package.
......@@ -167,9 +167,8 @@ class FixtureManipulatorTest extends PackageManagerKernelTestBase {
// Change a key in an existing package.
->setVersion('my/dev-package', '3.2.1', TRUE)
->commitChanges();
$this->assertSame(['foo' => 'bar'], $decode_composer_json('my/package')['extra']);
$this->assertSame(['foo' => 'bar'], $decode_packages_json()['packages']['my/package']['1.2.3']['extra']);
$this->assertSame('3.2.1', $this->inspector->getInstalledPackagesList($this->dir)['my/dev-package']?->version);
}
/**
......
......@@ -41,13 +41,21 @@ final class PackageManagerFixtureCreator {
$fixture_core_composer_data = $fixture_core_composer_file->read();
$fixture_core_composer_data['extra']['drupal-scaffold']['file-mapping'] = $core_composer_data['extra']['drupal-scaffold']['file-mapping'];
$fixture_core_composer_file->write($fixture_core_composer_data);
$fixture_packages_json = new JsonFile(static::FIXTURE_PATH . '/packages.json');
$fixture_packages_data = $fixture_packages_json->read();
foreach ($fixture_packages_data['packages']['drupal/core'] as &$release) {
$release['extra']['drupal-scaffold']['file-mapping'] = $core_composer_data['extra']['drupal-scaffold']['file-mapping'];
}
$fixture_packages_json->write($fixture_packages_data);
$fs = new Filesystem();
$fs->remove(static::FIXTURE_PATH . "/composer.lock");
// Remove all the vendor folders but leave our 2 test files.
// @see \Drupal\Tests\package_manager\Kernel\PathExcluder\VendorHardeningExcluderTest
self::removeAllExcept(self::FIXTURE_PATH . "/vendor", ['.htaccess', 'web.config']);
self::removeAllExcept(static::FIXTURE_PATH . "/vendor", ['.htaccess', 'web.config']);
static::doComposerInstall();
self::runComposerCommand(['install']);
static::removeAllExcept(static::FIXTURE_PATH . '/vendor/composer', ['installed.json', 'installed.php']);
$fs->remove(static::FIXTURE_PATH . '/vendor/autoload.php');
print "\nFixture updated.\nRunning phpcbf";
......@@ -70,7 +78,9 @@ final class PackageManagerFixtureCreator {
private static function runComposerCommand(array $command): string {
array_unshift($command, 'composer');
$command[] = "--working-dir=" . static::FIXTURE_PATH;
$process = new Process($command);
$process = new Process($command, env: [
'COMPOSER_MIRROR_PATH_REPOS' => '1',
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
......@@ -100,11 +110,4 @@ final class PackageManagerFixtureCreator {
}
}
/**
* Runs `composer install`.
*/
private static function doComposerInstall(): void {
self::runComposerCommand(['install']);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment