Commit 17459c60 authored by catch's avatar catch
Browse files

Issue #3540215 by phenaproxima, catch, benjifisher, dww, poker10: Remove the...

Issue #3540215 by phenaproxima, catch, benjifisher, dww, poker10: Remove the ability to configure the path to Composer

(cherry picked from commit 7dd237cb)
parent 164aa0be
Loading
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
executables:
  composer: ~
  rsync: ~
additional_trusted_composer_plugins: []
include_unknown_files_in_project_root: false
+0 −6
Original line number Diff line number Diff line
@@ -10,12 +10,6 @@ package_manager.settings:
  type: config_object
  label: 'Package Manager settings'
  mapping:
    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'
    additional_trusted_composer_plugins:
      type: sequence
      label: 'Additional trusted composer plugins'
+2 −0
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@ services:
  # provide additional functionality.
  Drupal\package_manager\ExecutableFinder:
    public: false
    calls:
      - [setLogger, ['@logger.channel.package_manager']]
    decorates: 'PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface'
  Drupal\package_manager\TranslatableStringFactory:
    public: false
+88 −10
Original line number Diff line number Diff line
@@ -5,8 +5,14 @@
namespace Drupal\package_manager;

use Composer\InstalledVersions;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Site\Settings;
use PhpTuf\ComposerStager\API\Exception\LogicException;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;

/**
 * An executable finder which looks for executable paths in configuration.
@@ -16,19 +22,27 @@
 *   at any time without warning. External code should not interact with this
 *   class.
 */
final class ExecutableFinder implements ExecutableFinderInterface {
final class ExecutableFinder implements ExecutableFinderInterface, LoggerAwareInterface {

  use LoggerAwareTrait;

  /**
   * The path where Composer is installed in the project, or FALSE if it's not.
   */
  private string|false|null $composerPath = NULL;
  private string|false|null $composerPackagePath = NULL;

  /**
   * The path of the Composer binary, or NULL if it can't be found.
   */
  private ?string $composerBinaryPath = NULL;

  public function __construct(
    private readonly ExecutableFinderInterface $decorated,
    private readonly FileSystemInterface $fileSystem,
    private readonly ConfigFactoryInterface $configFactory,
  ) {
    $this->composerPath = InstalledVersions::isInstalled('composer/composer')
      ? InstalledVersions::getInstallPath('composer/composer') . '/bin/composer'
    $this->composerPackagePath = InstalledVersions::isInstalled('composer/composer')
      ? InstalledVersions::getInstallPath('composer/composer')
      : FALSE;
  }

@@ -36,18 +50,82 @@ public function __construct(
   * {@inheritdoc}
   */
  public function find(string $name): string {
    $executables = $this->configFactory->get('package_manager.settings')
    $legacy_executables = $this->configFactory->get('package_manager.settings')
      ->get('executables');

    if (isset($executables[$name])) {
      return $executables[$name];
    if ($name === 'rsync') {
      try {
        return Settings::get('package_manager_rsync_path', $this->decorated->find($name));
      }
      catch (LogicException $e) {
        if (isset($legacy_executables[$name])) {
          @trigger_error("Storing the path to rsync in configuration is deprecated in drupal:11.2.4 and not supported in drupal:12.0.0. Move it to the <code>package_manager_rsync_path</code> setting instead. See https://www.drupal.org/node/3540264", E_USER_DEPRECATED);
          return $legacy_executables[$name];
        }
        throw $e;
      }
    }

    // If we're looking for Composer, use the project's local copy if available.
    if ($name === 'composer' && $this->composerPath && file_exists($this->composerPath)) {
      return $this->composerPath;
    elseif ($name === 'composer') {
      $path = $this->getLocalComposerPath();

      if ($path && file_exists($path)) {
        return $path;
      }

      // If the regular executable finder can't find Composer, and it's not
      // overridden by a setting, fall back to the configured path to Composer
      // (if available), which is no longer supported.
      try {
        return Settings::get('package_manager_composer_path', $this->decorated->find($name));
      }
      catch (LogicException $e) {
        if (isset($legacy_executables[$name])) {
          @trigger_error("Storing the path to Composer in configuration is deprecated in drupal:11.2.4 and not supported in drupal:12.0.0. Add composer/composer directly to your project's dependencies instead. See https://www.drupal.org/node/3540264", E_USER_DEPRECATED);
          return $legacy_executables[$name];
        }
        throw $e;
      }
    }
    return $this->decorated->find($name);
  }

  /**
   * Tries to find the Composer binary installed in the project.
   *
   * @return string|null
   *   The path of the `composer` binary installed in the project's vendor
   *   dependencies, or NULL if it is not installed or cannot be found.
   */
  private function getLocalComposerPath(): ?string {
    // Composer is not installed in the project, so there's nothing to do.
    if ($this->composerPackagePath === FALSE) {
      return NULL;
    }

    // This is a bit expensive to compute, so statically cache it.
    if ($this->composerBinaryPath) {
      return $this->composerBinaryPath;
    }

    $composer_json = file_get_contents($this->composerPackagePath . '/composer.json');
    $composer_json = Json::decode($composer_json);

    foreach ($composer_json['bin'] ?? [] as $bin) {
      if (str_ends_with($bin, '/composer')) {
        $bin = $this->composerPackagePath . '/' . $bin;

        // For extra security, try to disable the binary's execute permission.
        // If that fails, it's worth warning about but is not an actual problem.
        if (is_executable($bin) && !$this->fileSystem->chmod($bin, 0644)) {
          $this->logger?->warning('Composer was found at %path, but could not be made read-only.', [
            '%path' => $bin,
          ]);
        }
        return $this->composerBinaryPath = $bin;
      }
    }
    return NULL;
  }

}
+4 −4
Original line number Diff line number Diff line
@@ -54,15 +54,15 @@ public function help($route_name) : ?string {
        $output .= '    <p>' . $this->t('Ask your system administrator to remove <code>proc_open()</code> from the <a href=":url">disable_functions</a> setting in <code>php.ini</code>.', [':url' => 'https://www.php.net/manual/en/ini.core.php#ini.disable-functions']) . '</p>';
        $output .= '  </li>';
        $output .= '  <li>' . $this->t('What if it says the <code>composer</code> executable cannot be found?');
        $output .= '    <p>' . $this->t("If the <code>composer</code> executable's path cannot be automatically determined, it can be explicitly set by adding the following line to <code>settings.php</code>:") . '</p>';
        $output .= "    <pre><code>\$config['package_manager.settings']['executables']['composer'] = '/full/path/to/composer.phar';</code></pre>";
        $output .= '    <p>' . $this->t("If the <code>composer</code> executable's path cannot be automatically determined, you will need to add Composer to your project by running the following command: <code>composer require \"composer/composer:@version\"</code>:", [
          '@version' => ComposerInspector::SUPPORTED_VERSION,
        ]) . '</p>';
        $output .= '  </li>';
        $output .= '  <li>' . $this->t('What if it says the detected version of Composer is not supported?');
        $output .= '    <p>' . $this->t('The version of the <code>composer</code> executable must satisfy <code>@version</code>. See the <a href=":url">the Composer documentation</a> for more information, or use this command to update Composer:', [
        $output .= '    <p>' . $this->t('The version of the <code>composer</code> executable must satisfy <code>@version</code>. See the <a href=":url">the Composer documentation</a> for more information, or use this command to add Composer to your project: <code>composer require "composer/composer:@version"</code>', [
          '@version' => ComposerInspector::SUPPORTED_VERSION,
          ':url' => 'https://getcomposer.org/doc/03-cli.md#self-update-selfupdate',
        ]) . '</p>';
        $output .= '    <pre><code>composer self-update</code></pre>';
        $output .= '  </li>';
        $output .= '  <li>' . $this->t('What if it says the <code>composer validate</code> command failed?');
        $output .= '    <p>' . $this->t('Composer detected problems with your <code>composer.json</code> and/or <code>composer.lock</code> files, and the project is not in a completely valid state. See <a href=":url">the Composer documentation</a> for more information.', [':url' => 'https://getcomposer.org/doc/04-schema.md']) . '</p>';
Loading