summaryrefslogtreecommitdiffstatshomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/modules/package_manager/package_manager.services.yml5
-rw-r--r--core/modules/package_manager/src/ComposerRunner.php54
-rw-r--r--core/modules/package_manager/src/ProcessFactory.php97
-rw-r--r--core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php5
-rw-r--r--core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php36
-rw-r--r--core/modules/package_manager/tests/src/Kernel/ServicesTest.php6
-rw-r--r--core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php63
7 files changed, 123 insertions, 143 deletions
diff --git a/core/modules/package_manager/package_manager.services.yml b/core/modules/package_manager/package_manager.services.yml
index 059be554270..ead88254823 100644
--- a/core/modules/package_manager/package_manager.services.yml
+++ b/core/modules/package_manager/package_manager.services.yml
@@ -14,9 +14,6 @@ services:
Drupal\package_manager\ExecutableFinder:
public: false
decorates: 'PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface'
- Drupal\package_manager\ProcessFactory:
- public: false
- decorates: 'PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface'
Drupal\package_manager\TranslatableStringFactory:
public: false
decorates: 'PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface'
@@ -175,7 +172,7 @@ services:
PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface:
class: PhpTuf\ComposerStager\Internal\Process\Factory\ProcessFactory
PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface:
- class: PhpTuf\ComposerStager\Internal\Process\Service\ComposerProcessRunner
+ class: Drupal\package_manager\ComposerRunner
PhpTuf\ComposerStager\API\Process\Service\OutputCallbackInterface:
class: PhpTuf\ComposerStager\Internal\Process\Service\OutputCallback
PhpTuf\ComposerStager\API\Process\Service\ProcessInterface:
diff --git a/core/modules/package_manager/src/ComposerRunner.php b/core/modules/package_manager/src/ComposerRunner.php
new file mode 100644
index 00000000000..dbc029f6aa3
--- /dev/null
+++ b/core/modules/package_manager/src/ComposerRunner.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\File\FileSystemInterface;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+use PhpTuf\ComposerStager\API\Path\Value\PathInterface;
+use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
+use PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface;
+use PhpTuf\ComposerStager\API\Process\Service\OutputCallbackInterface;
+use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+// cspell:ignore BINDIR
+
+/**
+ * Runs Composer through the current PHP interpreter.
+ *
+ * @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.
+ */
+final class ComposerRunner implements ComposerProcessRunnerInterface {
+
+ public function __construct(
+ private readonly ExecutableFinderInterface $executableFinder,
+ private readonly ProcessFactoryInterface $processFactory,
+ private readonly FileSystemInterface $fileSystem,
+ private readonly ConfigFactoryInterface $configFactory,
+ ) {}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run(array $command, ?PathInterface $cwd = NULL, array $env = [], ?OutputCallbackInterface $callback = NULL, int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void {
+ // Run Composer through the PHP interpreter so we don't have to rely on
+ // PHP being in the PATH.
+ array_unshift($command, (new PhpExecutableFinder())->find(), $this->executableFinder->find('composer'));
+
+ $home = $this->fileSystem->getTempDirectory();
+ $home .= '/package_manager_composer_home-';
+ $home .= $this->configFactory->get('system.site')->get('uuid');
+ $this->fileSystem->prepareDirectory($home, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+
+ $process = $this->processFactory->create($command, $cwd, $env + ['COMPOSER_HOME' => $home]);
+ $process->setTimeout($timeout);
+ $process->mustRun($callback);
+ }
+
+}
diff --git a/core/modules/package_manager/src/ProcessFactory.php b/core/modules/package_manager/src/ProcessFactory.php
deleted file mode 100644
index 7e92977e7ff..00000000000
--- a/core/modules/package_manager/src/ProcessFactory.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\package_manager;
-
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\File\FileSystemInterface;
-use PhpTuf\ComposerStager\API\Path\Value\PathInterface;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
-use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
-
-// cspell:ignore BINDIR
-
-/**
- * Defines a process factory which sets the COMPOSER_HOME environment variable.
- *
- * @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.
- */
-final class ProcessFactory implements ProcessFactoryInterface {
-
- public function __construct(
- private readonly FileSystemInterface $fileSystem,
- private readonly ConfigFactoryInterface $configFactory,
- private readonly ProcessFactoryInterface $decorated,
- ) {}
-
- /**
- * {@inheritdoc}
- */
- public function create(array $command, ?PathInterface $cwd = NULL, array $env = []): ProcessInterface {
- $process = $this->decorated->create($command, $cwd, $env);
-
- $env = $process->getEnv();
- if ($command && $this->isComposerCommand($command)) {
- $env['COMPOSER_HOME'] = $this->getComposerHomePath();
- }
- // Ensure that the current PHP installation is the first place that will be
- // searched when looking for the PHP interpreter.
- $env['PATH'] = static::getPhpDirectory() . ':' . getenv('PATH');
- $process->setEnv($env);
- return $process;
- }
-
- /**
- * Returns the directory which contains the PHP interpreter.
- *
- * @return string
- * The path of the directory containing the PHP interpreter. If the server
- * is running in a command-line interface, the directory portion of
- * PHP_BINARY is returned; otherwise, the compile-time PHP_BINDIR is.
- *
- * @see php_sapi_name()
- * @see https://www.php.net/manual/en/reserved.constants.php
- */
- private static function getPhpDirectory(): string {
- if (PHP_SAPI === 'cli' || PHP_SAPI === 'cli-server') {
- return dirname(PHP_BINARY);
- }
- return PHP_BINDIR;
- }
-
- /**
- * Returns the path to use as the COMPOSER_HOME environment variable.
- *
- * @return string
- * The path which should be used as COMPOSER_HOME.
- */
- private function getComposerHomePath(): string {
- $home_path = $this->fileSystem->getTempDirectory();
- $home_path .= '/package_manager_composer_home-';
- $home_path .= $this->configFactory->get('system.site')->get('uuid');
- $this->fileSystem->prepareDirectory($home_path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
-
- return $home_path;
- }
-
- /**
- * Determines if a command is running Composer.
- *
- * @param string[] $command
- * The command parts.
- *
- * @return bool
- * TRUE if the command is running Composer, FALSE otherwise.
- */
- private function isComposerCommand(array $command): bool {
- $executable = $command[0];
- $executable_parts = explode('/', $executable);
- $file = array_pop($executable_parts);
- return str_starts_with($file, 'composer');
- }
-
-}
diff --git a/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php b/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
index 45046a8e2ad..15a7a2aab6b 100644
--- a/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
+++ b/core/modules/package_manager/tests/src/Functional/ComposerRequirementTest.php
@@ -47,7 +47,10 @@ class ComposerRequirementTest extends PackageManagerTestBase {
$config->set('executables.composer', '/path/to/composer')->save();
$this->getSession()->reload();
$assert_session->statusCodeEquals(200);
- $assert_session->pageTextContains('Composer was not found. The error message was: Failed to run process: The command "\'/path/to/composer\' \'--format=json\'" failed.');
+ $assert_session->pageTextContains('Composer was not found. The error message was: ');
+ // Check for the part of the command string that is constant (the path to
+ // the PHP interpreter will vary).
+ $assert_session->pageTextContains("/php' '/path/to/composer' '--format=json'\" failed.");
}
}
diff --git a/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php b/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php
deleted file mode 100644
index db27b06efa8..00000000000
--- a/core/modules/package_manager/tests/src/Kernel/ProcessFactoryTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\package_manager\Kernel;
-
-use Drupal\package_manager\ProcessFactory;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
-
-/**
- * @coversDefaultClass \Drupal\package_manager\ProcessFactory
- * @group auto_updates
- * @internal
- */
-class ProcessFactoryTest extends PackageManagerKernelTestBase {
-
- /**
- * Tests that the process factory prepends the PHP directory to PATH.
- */
- public function testPhpDirectoryPrependedToPath(): void {
- $factory = $this->container->get(ProcessFactoryInterface::class);
- $this->assertInstanceOf(ProcessFactory::class, $factory);
-
- // Ensure that the directory of the PHP interpreter can be found.
- $reflector = new \ReflectionObject($factory);
- $method = $reflector->getMethod('getPhpDirectory');
- $php_dir = $method->invoke(NULL);
- $this->assertNotEmpty($php_dir);
-
- // The process factory should always put the PHP interpreter's directory
- // at the beginning of the PATH environment variable.
- $env = $factory->create(['whoami'])->getEnv();
- $this->assertStringStartsWith("$php_dir:", $env['PATH']);
- }
-
-}
diff --git a/core/modules/package_manager/tests/src/Kernel/ServicesTest.php b/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
index a1db5a517c1..9e52f9b4075 100644
--- a/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
+++ b/core/modules/package_manager/tests/src/Kernel/ServicesTest.php
@@ -9,14 +9,12 @@ use Drupal\package_manager\ExecutableFinder;
use Drupal\package_manager\LoggingBeginner;
use Drupal\package_manager\LoggingCommitter;
use Drupal\package_manager\LoggingStager;
-use Drupal\package_manager\ProcessFactory;
use Drupal\package_manager\TranslatableStringFactory;
use Drupal\Tests\package_manager\Traits\AssertPreconditionsTrait;
use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
use PhpTuf\ComposerStager\API\Core\CommitterInterface;
use PhpTuf\ComposerStager\API\Core\StagerInterface;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
-use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
/**
@@ -38,11 +36,9 @@ class ServicesTest extends KernelTestBase {
* Tests that Package Manager's public services can be instantiated.
*/
public function testPackageManagerServices(): void {
- // Ensure that any overridden Composer Stager services were overridden
- // correctly.
+ // Ensure that certain Composer Stager services are decorated correctly.
$overrides = [
ExecutableFinderInterface::class => ExecutableFinder::class,
- ProcessFactoryInterface::class => ProcessFactory::class,
TranslatableFactoryInterface::class => TranslatableStringFactory::class,
BeginnerInterface::class => LoggingBeginner::class,
StagerInterface::class => LoggingStager::class,
diff --git a/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php b/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php
new file mode 100644
index 00000000000..91253d377d2
--- /dev/null
+++ b/core/modules/package_manager/tests/src/Unit/ComposerRunnerTest.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\package_manager\Unit;
+
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\package_manager\ComposerRunner;
+use Drupal\Tests\UnitTestCase;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\Group;
+use Prophecy\Argument;
+
+// cspell:ignore BINDIR
+
+/**
+ * Tests Package Manager's Composer runner service.
+ *
+ * @internal
+ */
+#[CoversClass(ComposerRunner::class)]
+#[Group('package_manager')]
+class ComposerRunnerTest extends UnitTestCase {
+
+ /**
+ * Tests that the Composer runner runs Composer through the PHP interpreter.
+ */
+ public function testRunner(): void {
+ $executable_finder = $this->prophesize(ExecutableFinderInterface::class);
+ $executable_finder->find('composer')
+ ->willReturn('/mock/composer')
+ ->shouldBeCalled();
+
+ $process_factory = $this->prophesize(ProcessFactoryInterface::class);
+ $process_factory->create(
+ // Internally, ComposerRunner uses Symfony's PhpExecutableFinder to locate
+ // the PHP interpreter, which should resolve to PHP_BINARY a command-line
+ // test environment.
+ [PHP_BINARY, '/mock/composer', '--version'],
+ NULL,
+ Argument::withKey('COMPOSER_HOME'),
+ )->shouldBeCalled();
+
+ $file_system = $this->prophesize(FileSystemInterface::class);
+ $file_system->getTempDirectory()->shouldBeCalled();
+ $file_system->prepareDirectory(Argument::cetera())->shouldBeCalled();
+
+ $runner = new ComposerRunner(
+ $executable_finder->reveal(),
+ $process_factory->reveal(),
+ $file_system->reveal(),
+ $this->getConfigFactoryStub([
+ 'system.site' => [
+ 'uuid' => 'testing',
+ ],
+ ]),
+ );
+ $runner->run(['--version']);
+ }
+
+}