From f0aa291b4babf1d809a86d490faa13ec55702ae3 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 14 Feb 2024 13:15:33 +0100 Subject: [PATCH] [FEATURE] Add support for composer.json only extensions The internal array always uses the extension key. Not having an ext_emconf.php with dependencies to extension keys, but only a composer.json with dependencies to package names will always result in: UnexpectedValueException: The package "extension_key" depends on "composer/package-key" which is not present in the system. This is a common situation in modern TYPO3 projects where multiple extensions are given which declare their internal dependencies to have proper TYPO3 loading order. A fallback from the dependency (which might be the composer name) to the actual extension key is added. Resolves: #541 --- Classes/Core/PackageCollection.php | 14 ++-- .../Core/Fixtures/Packages/PackageStates.php | 31 +++++++++ .../Fixtures/Packages/package0/composer.json | 17 +++++ .../Fixtures/Packages/package1/composer.json | 20 ++++++ .../Fixtures/Packages/package2/composer.json | 19 +++++ Tests/Unit/Core/PackageCollectionTest.php | 69 +++++++++++++++++++ 6 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 Tests/Unit/Core/Fixtures/Packages/PackageStates.php create mode 100644 Tests/Unit/Core/Fixtures/Packages/package0/composer.json create mode 100644 Tests/Unit/Core/Fixtures/Packages/package1/composer.json create mode 100644 Tests/Unit/Core/Fixtures/Packages/package2/composer.json create mode 100644 Tests/Unit/Core/PackageCollectionTest.php diff --git a/Classes/Core/PackageCollection.php b/Classes/Core/PackageCollection.php index b25018b3..3ef2cf86 100644 --- a/Classes/Core/PackageCollection.php +++ b/Classes/Core/PackageCollection.php @@ -157,7 +157,8 @@ protected function convertConfigurationForGraph(array $allPackageConstraints, ar ]; if (isset($allPackageConstraints[$packageKey]['dependencies'])) { foreach ($allPackageConstraints[$packageKey]['dependencies'] as $dependentPackageKey) { - if (!in_array($dependentPackageKey, $packageKeys, true)) { + $extensionKey = $this->composerPackageManager->getPackageInfo($dependentPackageKey)?->getExtensionKey() ?? ''; + if (!in_array($dependentPackageKey, $packageKeys, true) && !in_array($extensionKey, $packageKeys, true)) { if ($this->isComposerDependency($dependentPackageKey)) { // The given package has a dependency to a Composer package that has no relation to TYPO3 // We can ignore those, when calculating the extension order @@ -169,7 +170,7 @@ protected function convertConfigurationForGraph(array $allPackageConstraints, ar 1519931815 ); } - $dependencies[$packageKey]['after'][] = $dependentPackageKey; + $dependencies[$packageKey]['after'][] = $extensionKey ?: $dependentPackageKey; } } if (isset($allPackageConstraints[$packageKey]['suggestions'])) { @@ -250,15 +251,18 @@ protected function getDependencyArrayForPackage(PackageInterface $package, array foreach ($dependentPackageConstraints as $constraint) { if ($constraint instanceof PackageConstraint) { $dependentPackageKey = $constraint->getValue(); + $extensionKey = $this->composerPackageManager->getPackageInfo($dependentPackageKey)?->getExtensionKey() ?? ''; if (!in_array($dependentPackageKey, $dependentPackageKeys, true) && !in_array($dependentPackageKey, $trace, true)) { - $dependentPackageKeys[] = $dependentPackageKey; + $dependentPackageKeys[] = $extensionKey ?: $dependentPackageKey; } - if (!isset($this->packages[$dependentPackageKey])) { + + if (!isset($this->packages[$dependentPackageKey]) && !isset($this->packages[$extensionKey])) { if ($this->isComposerDependency($dependentPackageKey)) { // The given package has a dependency to a Composer package that has no relation to TYPO3 // We can ignore those, when calculating the extension order continue; } + throw new Exception( sprintf( 'Package "%s" depends on package "%s" which does not exist.', @@ -268,7 +272,7 @@ protected function getDependencyArrayForPackage(PackageInterface $package, array 1695119749 ); } - $this->getDependencyArrayForPackage($this->packages[$dependentPackageKey], $dependentPackageKeys, $trace); + $this->getDependencyArrayForPackage($this->packages[$dependentPackageKey] ?? $this->packages[$extensionKey], $dependentPackageKeys, $trace); } } return array_reverse($dependentPackageKeys); diff --git a/Tests/Unit/Core/Fixtures/Packages/PackageStates.php b/Tests/Unit/Core/Fixtures/Packages/PackageStates.php new file mode 100644 index 00000000..53ee2731 --- /dev/null +++ b/Tests/Unit/Core/Fixtures/Packages/PackageStates.php @@ -0,0 +1,31 @@ + [ + 'core' => [ + 'packagePath' => '.Build/vendor/typo3/cms-core/', + ], + 'extbase' => [ + 'packagePath' => '.Build/vendor/typo3/cms-extbase/', + ], + 'fluid' => [ + 'packagePath' => '.Build/vendor/typo3/cms-fluid/', + ], + 'backend' => [ + 'packagePath' => '.Build/vendor/typo3/cms-backend/', + ], + 'frontend' => [ + 'packagePath' => '.Build/vendor/typo3/cms-frontend/', + ], + 'package0' => [ + 'packagePath' => 'Tests/Unit/Core/Fixtures/Packages/package0/', + ], + 'package2' => [ + 'packagePath' => 'Tests/Unit/Core/Fixtures/Packages/package2/', + ], + 'package1' => [ + 'packagePath' => 'Tests/Unit/Core/Fixtures/Packages/package1/', + ], + ], + 'version' => 5, +]; diff --git a/Tests/Unit/Core/Fixtures/Packages/package0/composer.json b/Tests/Unit/Core/Fixtures/Packages/package0/composer.json new file mode 100644 index 00000000..96c0eab4 --- /dev/null +++ b/Tests/Unit/Core/Fixtures/Packages/package0/composer.json @@ -0,0 +1,17 @@ +{ + "name": "typo3/testing-framework-package-0", + "description": "Package 0", + "type": "typo3-cms-extension", + "license": [ + "GPL-2.0-or-later" + ], + "require": { + "php": "*", + "typo3/cms-core": "*" + }, + "extra": { + "typo3/cms": { + "extension-key": "package0" + } + } +} diff --git a/Tests/Unit/Core/Fixtures/Packages/package1/composer.json b/Tests/Unit/Core/Fixtures/Packages/package1/composer.json new file mode 100644 index 00000000..8171c655 --- /dev/null +++ b/Tests/Unit/Core/Fixtures/Packages/package1/composer.json @@ -0,0 +1,20 @@ +{ + "name": "typo3/testing-framework-package-1", + "description": "Package 1, with replace entry", + "type": "typo3-cms-extension", + "license": [ + "GPL-2.0-or-later" + ], + "require": { + "php": "*", + "typo3/cms-core": "*" + }, + "replace": { + "typo3-ter/package1": "self.version" + }, + "extra": { + "typo3/cms": { + "extension-key": "package1" + } + } +} diff --git a/Tests/Unit/Core/Fixtures/Packages/package2/composer.json b/Tests/Unit/Core/Fixtures/Packages/package2/composer.json new file mode 100644 index 00000000..478fff8a --- /dev/null +++ b/Tests/Unit/Core/Fixtures/Packages/package2/composer.json @@ -0,0 +1,19 @@ +{ + "name": "typo3/testing-framework-package-2", + "description": "Package 2 depending on package 1", + "type": "typo3-cms-extension", + "license": [ + "GPL-2.0-or-later" + ], + "require": { + "php": "*", + "typo3/testing-framework-package-1": "*", + "typo3/testing-framework-package-0": "*", + "typo3/cms-core": "*" + }, + "extra": { + "typo3/cms": { + "extension-key": "package2" + } + } +} diff --git a/Tests/Unit/Core/PackageCollectionTest.php b/Tests/Unit/Core/PackageCollectionTest.php new file mode 100644 index 00000000..5572a17f --- /dev/null +++ b/Tests/Unit/Core/PackageCollectionTest.php @@ -0,0 +1,69 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace Typo3\TestingFramework\Tests\Unit\Core; + +use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\Service\DependencyOrderingService; +use TYPO3\TestingFramework\Composer\ComposerPackageManager; +use TYPO3\TestingFramework\Core\PackageCollection; + +final class PackageCollectionTest extends TestCase +{ + /** + * @test + */ + public function sortsComposerPackages(): void + { + $packageStates = require __DIR__ . '/Fixtures/Packages/PackageStates.php'; + $packageStates = $packageStates['packages']; + $basePath = realpath(__DIR__ . '/../../../'); + + $composerPackageManager = new ComposerPackageManager(); + // That way it knows about the extensions, this is done by TestBase upfront. + $composerPackageManager->getPackageInfoWithFallback(__DIR__ . '/Fixtures/Packages/package0'); + $composerPackageManager->getPackageInfoWithFallback(__DIR__ . '/Fixtures/Packages/package1'); + $composerPackageManager->getPackageInfoWithFallback(__DIR__ . '/Fixtures/Packages/package2'); + + $subject = PackageCollection::fromPackageStates( + $composerPackageManager, + new PackageManager( + new DependencyOrderingService(), + __DIR__ . '/Fixtures/Packages/PackageStates.php', + $basePath + ), + $basePath, + $packageStates + ); + + $result = $subject->sortPackageStates( + $packageStates, + new DependencyOrderingService() + ); + + self::assertSame(7, array_search('package2', array_keys($result)), 'Package 2 is not stored at loading order 7.'); + self::assertSame(6, array_search('package1', array_keys($result)), 'Package 1 is not stored at loading order 6.'); + self::assertSame(5, array_search('package0', array_keys($result)), 'Package 0 is not stored at loading order 5.'); + } +}