TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use Composer\Util\Filesystem as FilesystemUtility;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use TYPO3\CMS\Composer\Plugin\Config;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScript;
|
||||
|
||||
class CliEntryPoint implements InstallerScript
|
||||
{
|
||||
/**
|
||||
* Absolute path to entry script source
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* The target file relative to the web directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $target;
|
||||
|
||||
public function __construct(string $source, string $target)
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
public function run(Event $event): bool
|
||||
{
|
||||
$composer = $event->getComposer();
|
||||
$filesystemUtility = new FilesystemUtility();
|
||||
$filesystem = new Filesystem();
|
||||
$pluginConfig = Config::load($composer);
|
||||
|
||||
$entryPointContent = file_get_contents($this->source);
|
||||
if ($entryPointContent === false) {
|
||||
return false;
|
||||
}
|
||||
$targetFile = $pluginConfig->get('root-dir') . '/' . $this->target;
|
||||
$autoloadFile = $composer->getConfig()->get('vendor-dir') . '/autoload.php';
|
||||
|
||||
$entryPointContent = preg_replace(
|
||||
'/__DIR__ . \'[^\']*\'/',
|
||||
$filesystemUtility->findShortestPathCode($targetFile, $autoloadFile),
|
||||
$entryPointContent
|
||||
);
|
||||
|
||||
$filesystemUtility->ensureDirectoryExists(dirname($targetFile));
|
||||
$filesystem->dumpFile($targetFile, $entryPointContent);
|
||||
$filesystem->chmod($targetFile, 0755);
|
||||
|
||||
return $filesystem->exists($targetFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
class CommandExecutionFailedException extends \Exception
|
||||
{
|
||||
public function __construct(
|
||||
public array $typo3Command,
|
||||
public string $errorOutput = '',
|
||||
int $code = 1765277390,
|
||||
) {
|
||||
$message = sprintf('Failed to run command %s', implode(' ', $this->typo3Command));
|
||||
$message .= chr(10) . $this->errorOutput;
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScript;
|
||||
|
||||
final readonly class ConsoleCommand implements InstallerScript
|
||||
{
|
||||
public function __construct(
|
||||
private array $command,
|
||||
private string $message = '',
|
||||
) {}
|
||||
|
||||
public function run(Event $event): bool
|
||||
{
|
||||
$io = $event->getIO();
|
||||
if ($this->message) {
|
||||
$io->writeError(sprintf('<info>%s</info>', $this->message));
|
||||
}
|
||||
try {
|
||||
$this->executeProcess($event);
|
||||
} catch (CommandExecutionFailedException $e) {
|
||||
$io->writeError(sprintf('<error>%s</error>', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function executeProcess(Event $event): void
|
||||
{
|
||||
$io = $event->getIO();
|
||||
$typo3Command = $this->getTypo3Command($event);
|
||||
array_unshift($typo3Command, ...$this->getPhpExecCommand());
|
||||
|
||||
$process = new ProcessExecutor($io);
|
||||
$exitCode = $process->execute($typo3Command, $commandOutput);
|
||||
if ($exitCode !== 0) {
|
||||
$errorOutput = trim($commandOutput);
|
||||
if ($process->getErrorOutput() !== '') {
|
||||
$errorOutput .= chr(10) . $process->getErrorOutput();
|
||||
}
|
||||
throw new CommandExecutionFailedException(
|
||||
$this->command,
|
||||
$errorOutput,
|
||||
1765283208,
|
||||
);
|
||||
}
|
||||
$io->writeError($commandOutput, false);
|
||||
}
|
||||
|
||||
private function getTypo3Command(Event $event): array
|
||||
{
|
||||
$composer = $event->getComposer();
|
||||
$binDir = $composer->getConfig()->get('bin-dir');
|
||||
|
||||
$finder = new ExecutableFinder();
|
||||
$pathToTypo3Binary = $finder->find('typo3', null, [$binDir]);
|
||||
if ($pathToTypo3Binary === null) {
|
||||
throw new \RuntimeException('Could not determine path to typo3 binary', 1765273845);
|
||||
}
|
||||
if (Platform::isWindows()) {
|
||||
$pathToTypo3BinaryWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToTypo3Binary);
|
||||
// prefer non-extension file if it exists when executing with PHP
|
||||
if (file_exists($pathToTypo3BinaryWithoutExt)) {
|
||||
$pathToTypo3Binary = $pathToTypo3BinaryWithoutExt;
|
||||
}
|
||||
unset($pathToTypo3BinaryWithoutExt);
|
||||
}
|
||||
$typo3Command = $this->command;
|
||||
array_unshift($typo3Command, $pathToTypo3Binary);
|
||||
return $typo3Command;
|
||||
}
|
||||
|
||||
private function getPhpExecCommand(): array
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
$phpPath = $finder->find(false);
|
||||
if (!$phpPath) {
|
||||
throw new \RuntimeException('Failed to locate PHP binary to execute ' . $phpPath, 1765274260);
|
||||
}
|
||||
$phpArgs = $finder->findArguments();
|
||||
array_unshift($phpArgs, $phpPath);
|
||||
$phpArgs[] = '-d';
|
||||
$phpArgs[] = 'allow_url_fopen=' . ini_get('allow_url_fopen');
|
||||
$phpArgs[] = '-d';
|
||||
$phpArgs[] = 'disable_functions=' . ini_get('disable_functions');
|
||||
$phpArgs[] = '-d';
|
||||
$phpArgs[] = 'memory_limit=' . ini_get('memory_limit');
|
||||
return $phpArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use TYPO3\CMS\Composer\Plugin\Config;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScript;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
final class FrameworkPackageWriter implements InstallerScript
|
||||
{
|
||||
private const string CORE_RESOURCE_PATH = '/typo3/sysext/core/Resources/Private/Php/framework-packages.php';
|
||||
|
||||
public function run(Event $event): bool
|
||||
{
|
||||
$config = Config::load($event->getComposer(), $event->getIO());
|
||||
$basePath = $config->get('base-dir');
|
||||
$frameworkPackageNames = $this->getFrameworkPackageNames($config);
|
||||
$io = $event->getIO();
|
||||
$io->writeError('TYPO3: Dumping framework package names', true, $io::VERBOSE);
|
||||
file_put_contents(
|
||||
$basePath . self::CORE_RESOURCE_PATH,
|
||||
'<?php'
|
||||
. chr(10)
|
||||
. chr(10)
|
||||
. 'return '
|
||||
. ArrayUtility::arrayExport($frameworkPackageNames)
|
||||
. ';'
|
||||
. chr(10)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getFrameworkPackageNames(Config $config): array
|
||||
{
|
||||
$typo3Json = file_get_contents($config->get('base-dir') . '/composer.json');
|
||||
if ($typo3Json === false) {
|
||||
throw new \RuntimeException('The main TYPO3 composer.json file was not found.', 1774091461);
|
||||
}
|
||||
return array_keys(
|
||||
array_filter(
|
||||
json_decode($typo3Json, true, 512, JSON_THROW_ON_ERROR)['replace'] ?? [],
|
||||
static fn($value) => $value === 'self.version'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScripts\EntryPoint;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScriptsRegistration;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\ScriptDispatcher;
|
||||
|
||||
/**
|
||||
* Hook into Composer build to generate TYPO3 cli tool entry script
|
||||
* @internal only used for TYPO3 internally for setting up the installation.
|
||||
*/
|
||||
readonly class InstallerScripts implements InstallerScriptsRegistration
|
||||
{
|
||||
public static function register(Event $event, ScriptDispatcher $scriptDispatcher)
|
||||
{
|
||||
$scriptDispatcher->addInstallerScript(
|
||||
new EntryPoint(
|
||||
dirname(__DIR__, 2) . '/Resources/Private/Php/index.php',
|
||||
'index.php'
|
||||
)
|
||||
);
|
||||
|
||||
if ($event->getComposer()->getPackage()->getName() === 'typo3/cms') {
|
||||
// We only need to provide the binary in monorepo classic mode (regular Composer mode receives it via typo3/cms-cli)
|
||||
$source = dirname(__DIR__, 2) . '/Resources/Private/Php/cli.php';
|
||||
$target = 'typo3/sysext/core/bin/typo3';
|
||||
$scriptDispatcher->addInstallerScript(new CliEntryPoint($source, $target));
|
||||
$scriptDispatcher->addInstallerScript(new FrameworkPackageWriter());
|
||||
} else {
|
||||
// Provide package artifact in regular composer mode (not needed for monorepo classic mode)
|
||||
$scriptDispatcher->addInstallerScript(
|
||||
new PackageArtifactBuilder()
|
||||
);
|
||||
if (!getenv('TYPO3_SKIP_ASSET_PUBLISH')) {
|
||||
$command = ['asset:publish'];
|
||||
if ($event->getIO()->isVerbose()) {
|
||||
$command[] = '-v';
|
||||
}
|
||||
$scriptDispatcher->addInstallerScript(
|
||||
new ConsoleCommand(
|
||||
$command
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Composer;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Util\Filesystem;
|
||||
use TYPO3\CMS\Composer\Plugin\Config;
|
||||
use TYPO3\CMS\Composer\Plugin\Core\InstallerScript;
|
||||
use TYPO3\CMS\Composer\Plugin\Util\ExtensionKeyResolver;
|
||||
use TYPO3\CMS\Core\Package\Cache\ComposerPackageArtifact;
|
||||
use TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException;
|
||||
use TYPO3\CMS\Core\Package\Exception\InvalidPackageManifestException;
|
||||
use TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException;
|
||||
use TYPO3\CMS\Core\Package\Exception\InvalidPackageStateException;
|
||||
use TYPO3\CMS\Core\Package\Package;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Package\VirtualAppPackage;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* The builder is a subclass of PackageManager as it shares much of its functionality.
|
||||
* It evaluates the installed Composer packages for applicable TYPO3 extensions.
|
||||
* All Composer packages will be discovered, that have an extra.typo3/cms definition in their composer.json.
|
||||
*
|
||||
* @template packageMap of array<int, array{PackageInterface, string, non-empty-string}>
|
||||
* @template IOMessage of array{severity: 'title'|'info'|'warning', verbosity: int, message: string}
|
||||
*
|
||||
* @internal This class is an implementation detail and does not represent public API
|
||||
*/
|
||||
class PackageArtifactBuilder extends PackageManager implements InstallerScript
|
||||
{
|
||||
/**
|
||||
* @var Event $event
|
||||
*/
|
||||
private $event;
|
||||
|
||||
/**
|
||||
* @var Config $config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var Filesystem $fileSystem
|
||||
*/
|
||||
private $fileSystem;
|
||||
|
||||
private array $installedTypo3Extensions = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Disable path determination with Environment class, which is not initialized here
|
||||
parent::__construct(new DependencyOrderingService(), '', '');
|
||||
}
|
||||
|
||||
public function isComposerDependency(string $packageName): bool
|
||||
{
|
||||
return !in_array($packageName, $this->installedTypo3Extensions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry method called in Composer post-dump-autoload hook
|
||||
*
|
||||
* @throws InvalidPackageKeyException
|
||||
* @throws InvalidPackageManifestException
|
||||
* @throws InvalidPackagePathException
|
||||
* @throws InvalidPackageStateException
|
||||
*/
|
||||
public function run(Event $event): bool
|
||||
{
|
||||
$io = $event->getIO();
|
||||
$this->event = $event;
|
||||
$this->config = Config::load($this->event->getComposer(), $io);
|
||||
$this->fileSystem = new Filesystem();
|
||||
$composer = $this->event->getComposer();
|
||||
$basePath = $this->config->get('base-dir');
|
||||
$this->packagesBasePath = $basePath . '/';
|
||||
foreach ($this->extractPackageMapFromComposer() as [$composerPackage, $path, $extensionKey]) {
|
||||
$packagePath = PathUtility::sanitizeTrailingSeparator($path);
|
||||
$package = new Package($this, $extensionKey, $packagePath, true);
|
||||
$package->getPackageMetaData()->setVersion($composerPackage->getPrettyVersion());
|
||||
$this->registerPackage($package);
|
||||
}
|
||||
$this->sortPackagesAndConfiguration();
|
||||
$appPackage = new VirtualAppPackage(
|
||||
$this,
|
||||
$this->packagesBasePath,
|
||||
rtrim($this->config->get('web-dir', $this->config::RELATIVE_PATHS), '/') . '/',
|
||||
);
|
||||
$this->registerPackage($appPackage);
|
||||
$this->packageStatesConfiguration['packages'][$appPackage->getPackageKey()] = [];
|
||||
$cacheIdentifier = md5(serialize($composer->getLocker()->getLockData()) . $this->event->isDevMode());
|
||||
$this->setPackageCache(new ComposerPackageArtifact($composer->getConfig()->get('vendor-dir') . '/typo3', $this->fileSystem, $cacheIdentifier));
|
||||
$this->validateResources();
|
||||
$this->saveToPackageCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make package paths of all packages relative
|
||||
* so that it does not matter in which environment
|
||||
* the "composer install" operation is performed
|
||||
*/
|
||||
protected function saveToPackageCache(): void
|
||||
{
|
||||
$basePath = $this->config->get('base-dir');
|
||||
foreach ($this->packages as $package) {
|
||||
if ($package instanceof Package) {
|
||||
$package->makePathRelative($this->fileSystem, $basePath);
|
||||
}
|
||||
}
|
||||
parent::saveToPackageCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts all TYPO3 extension packages by dependency defined in composer.json file
|
||||
*/
|
||||
private function sortPackagesAndConfiguration(): void
|
||||
{
|
||||
$packagesWithDependencies = $this->resolvePackageDependencies($this->packages);
|
||||
// Sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards
|
||||
ksort($packagesWithDependencies);
|
||||
$sortedPackageKeys = $this->sortPackageStatesConfigurationByDependency($packagesWithDependencies);
|
||||
$this->packageStatesConfiguration = [];
|
||||
$sortedPackages = [];
|
||||
foreach ($sortedPackageKeys as $packageKey) {
|
||||
$sortedPackages[$packageKey] = $this->packages[$packageKey];
|
||||
// The artifact does not need path information, so it is kept empty
|
||||
// The keys must be present, though because the PackageManager implies than a
|
||||
// package is active by this configuration array
|
||||
$this->packageStatesConfiguration['packages'][$packageKey] = [];
|
||||
}
|
||||
$this->packages = $sortedPackages;
|
||||
$this->packageStatesConfiguration['version'] = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a map of all installed packages and filter them, when they apply
|
||||
* for TYPO3.
|
||||
*
|
||||
* @return packageMap
|
||||
*/
|
||||
private function extractPackageMapFromComposer(): array
|
||||
{
|
||||
$composer = $this->event->getComposer();
|
||||
$rootPackage = $composer->getPackage();
|
||||
$autoLoadGenerator = $composer->getAutoloadGenerator();
|
||||
$localRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
return array_map(
|
||||
function (array $packageAndPath) use ($rootPackage): array {
|
||||
[$composerPackage, $packagePath] = $packageAndPath;
|
||||
$packageName = $composerPackage->getName();
|
||||
$packagePath = GeneralUtility::fixWindowsFilePath($packagePath);
|
||||
try {
|
||||
$extensionKey = ExtensionKeyResolver::resolve($composerPackage);
|
||||
} catch (\Throwable $e) {
|
||||
if (str_starts_with($composerPackage->getType(), 'typo3-cms-')) {
|
||||
// This means we have a package of type extension, and it does not have the extension key set
|
||||
// This only happens since version > 4.0 of the installer and must be propagated to become user facing
|
||||
throw $e;
|
||||
}
|
||||
// In case we can not otherwise determine the extension key, we take the composer name
|
||||
$extensionKey = $packageName;
|
||||
}
|
||||
if (isset($this->installedTypo3Extensions[$extensionKey])) {
|
||||
throw new \UnexpectedValueException(
|
||||
sprintf(
|
||||
'Package with the name "%s" registered extension key "%s", but this key was already set by package with the name "%s"',
|
||||
$packageName,
|
||||
$extensionKey,
|
||||
$this->installedTypo3Extensions[$extensionKey]
|
||||
),
|
||||
1638880941
|
||||
);
|
||||
}
|
||||
$this->installedTypo3Extensions[$extensionKey] = $packageName;
|
||||
$this->composerNameToPackageKeyMap[$packageName] = $extensionKey;
|
||||
if ($composerPackage === $rootPackage) {
|
||||
// The root package's path is the Composer base dir
|
||||
$packagePath = $this->config->get('base-dir');
|
||||
}
|
||||
// Add extension key to the package map for later reference
|
||||
return [$composerPackage, $packagePath, $extensionKey];
|
||||
},
|
||||
array_filter(
|
||||
$autoLoadGenerator->buildPackageMap($composer->getInstallationManager(), $rootPackage, $localRepo->getCanonicalPackages()),
|
||||
static function (array $packageAndPath): bool {
|
||||
/** @var PackageInterface $composerPackage */
|
||||
[$composerPackage] = $packageAndPath;
|
||||
return isset($composerPackage->getExtra()['typo3/cms']);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user