From 8a33711202bb36902991f8620d0d1df5b531dcec Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Tue, 30 Nov 2021 22:06:11 +0000
Subject: [PATCH] Issue #3227420 by phenaproxima, tedbow: Allow paths to
 required executables to be configurable

---
 .../install/package_manager.settings.yml      |  3 ++
 .../config/schema/package_manager.schema.yml  |  6 +++
 package_manager/package_manager.services.yml  |  3 +-
 package_manager/src/ExecutableFinder.php      | 52 +++++++++++++++++++
 .../tests/src/Kernel/ExecutableFinderTest.php | 39 ++++++++++++++
 5 files changed, 102 insertions(+), 1 deletion(-)
 create mode 100644 package_manager/src/ExecutableFinder.php
 create mode 100644 package_manager/tests/src/Kernel/ExecutableFinderTest.php

diff --git a/package_manager/config/install/package_manager.settings.yml b/package_manager/config/install/package_manager.settings.yml
index 3ba0dbe670..bbe571a9cb 100644
--- a/package_manager/config/install/package_manager.settings.yml
+++ b/package_manager/config/install/package_manager.settings.yml
@@ -1 +1,4 @@
 file_syncer: php
+executables:
+  composer: ~
+  rsync: ~
diff --git a/package_manager/config/schema/package_manager.schema.yml b/package_manager/config/schema/package_manager.schema.yml
index deb149ad41..1d02937177 100644
--- a/package_manager/config/schema/package_manager.schema.yml
+++ b/package_manager/config/schema/package_manager.schema.yml
@@ -5,3 +5,9 @@ package_manager.settings:
     file_syncer:
       type: string
       label: 'Which file syncer to use, or NULL to auto-detect'
+    executables:
+      type: sequence
+      label: 'Absolute paths to required executables, or NULL to rely on PATH'
+      sequence:
+        type: string
+        label: 'Absolute path to executable, or NULL'
diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml
index 63da64d195..0ffa5aa01a 100644
--- a/package_manager/package_manager.services.yml
+++ b/package_manager/package_manager.services.yml
@@ -18,9 +18,10 @@ services:
     arguments:
       - '@package_manager.symfony_file_system'
   package_manager.executable_finder:
-    class: PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinder
+    class: Drupal\package_manager\ExecutableFinder
     arguments:
       - '@package_manager.symfony_executable_finder'
+      - '@config.factory'
 
   # Executable runners.
   package_manager.rsync_runner:
diff --git a/package_manager/src/ExecutableFinder.php b/package_manager/src/ExecutableFinder.php
new file mode 100644
index 0000000000..d18f45be13
--- /dev/null
+++ b/package_manager/src/ExecutableFinder.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\package_manager;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface;
+use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinder as StagerExecutableFinder;
+use Symfony\Component\Process\ExecutableFinder as SymfonyExecutableFinder;
+
+/**
+ * An executable finder which looks for executable paths in configuration.
+ */
+class ExecutableFinder implements ExecutableFinderInterface {
+
+  /**
+   * The decorated executable finder.
+   *
+   * @var \PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinder
+   */
+  private $decorated;
+
+  /**
+   * The config factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  private $configFactory;
+
+  /**
+   * Constructs an ExecutableFinder object.
+   *
+   * @param \Symfony\Component\Process\ExecutableFinder $symfony_executable_finder
+   *   The Symfony executable finder.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   */
+  public function __construct(SymfonyExecutableFinder $symfony_executable_finder, ConfigFactoryInterface $config_factory) {
+    $this->decorated = new StagerExecutableFinder($symfony_executable_finder);
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function find(string $name): string {
+    $executables = $this->configFactory->get('package_manager.settings')
+      ->get('executables');
+
+    return $executables[$name] ?? $this->decorated->find($name);
+  }
+
+}
diff --git a/package_manager/tests/src/Kernel/ExecutableFinderTest.php b/package_manager/tests/src/Kernel/ExecutableFinderTest.php
new file mode 100644
index 0000000000..fb59c652ca
--- /dev/null
+++ b/package_manager/tests/src/Kernel/ExecutableFinderTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Tests\package_manager\Kernel;
+
+use Symfony\Component\Process\ExecutableFinder as SymfonyExecutableFinder;
+
+/**
+ * @covers \Drupal\package_manager\ExecutableFinder
+ *
+ * @group package_manager
+ */
+class ExecutableFinderTest extends PackageManagerKernelTestBase {
+
+  /**
+   * Tests that the executable finder looks for paths in configuration.
+   */
+  public function testCheckConfigurationForExecutablePath(): void {
+    $symfony_executable_finder = new class () extends SymfonyExecutableFinder {
+
+      /**
+       * {@inheritdoc}
+       */
+      public function find($name, $default = NULL, array $extraDirs = []) {
+        return '/dev/null';
+      }
+
+    };
+    $this->container->set('package_manager.symfony_executable_finder', $symfony_executable_finder);
+
+    $this->config('package_manager.settings')
+      ->set('executables.composer', '/path/to/composer')
+      ->save();
+
+    $executable_finder = $this->container->get('package_manager.executable_finder');
+    $this->assertSame('/path/to/composer', $executable_finder->find('composer'));
+    $this->assertSame('/dev/null', $executable_finder->find('rsync'));
+  }
+
+}
-- 
GitLab