Skip to content

Commit

Permalink
[FEATURE] Add support for composer.json only extensions
Browse files Browse the repository at this point in the history
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: TYPO3#541
  • Loading branch information
DanielSiepmann authored and sbuerk committed Nov 11, 2024
1 parent 302dd83 commit f0aa291
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 5 deletions.
14 changes: 9 additions & 5 deletions Classes/Core/PackageCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'])) {
Expand Down Expand Up @@ -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.',
Expand All @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions Tests/Unit/Core/Fixtures/Packages/PackageStates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

return [
'packages' => [
'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,
];
17 changes: 17 additions & 0 deletions Tests/Unit/Core/Fixtures/Packages/package0/composer.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
20 changes: 20 additions & 0 deletions Tests/Unit/Core/Fixtures/Packages/package1/composer.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
19 changes: 19 additions & 0 deletions Tests/Unit/Core/Fixtures/Packages/package2/composer.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
69 changes: 69 additions & 0 deletions Tests/Unit/Core/PackageCollectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

/*
* Copyright (C) 2024 Daniel Siepmann <[email protected]>
*
* 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.');
}
}

0 comments on commit f0aa291

Please sign in to comment.