diff --git a/core/modules/package_manager/package_manager.install b/core/modules/package_manager/package_manager.install index 22d42a346ead7fc5b93e132b14b7f150bf057528..840eaf5b0c2d74161af9c94f3d55c5a6151375be 100644 --- a/core/modules/package_manager/package_manager.install +++ b/core/modules/package_manager/package_manager.install @@ -12,8 +12,27 @@ use Drupal\package_manager\Exception\StageFailureMarkerException; use Drupal\package_manager\FailureMarker; use PhpTuf\ComposerStager\API\Exception\ExceptionInterface; +use PhpTuf\ComposerStager\API\Exception\LogicException; use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface; +/** + * Implements hook_install(). + */ +function package_manager_install(): void { + $executable_finder = \Drupal::service(ExecutableFinderInterface::class); + + $config = \Drupal::configFactory()->getEditable('package_manager.settings'); + foreach (['composer', 'rsync'] as $executable_name) { + try { + $config->set("executables.$executable_name", $executable_finder->find($executable_name)); + } + catch (LogicException) { + // Couldn't find the executable; don't change the config. + } + } + $config->save(); +} + /** * Implements hook_requirements(). */ diff --git a/core/modules/package_manager/package_manager.links.menu.yml b/core/modules/package_manager/package_manager.links.menu.yml new file mode 100644 index 0000000000000000000000000000000000000000..65fe5e04146eb8625414b63c4142d762713c0f8b --- /dev/null +++ b/core/modules/package_manager/package_manager.links.menu.yml @@ -0,0 +1,5 @@ +package_manager.settings: + title: 'Package Manager' + parent: system.admin_config_development + description: 'Configure Package Manager settings.' + route_name: package_manager.settings diff --git a/core/modules/package_manager/package_manager.routing.yml b/core/modules/package_manager/package_manager.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..e0e347c23b35f02750e4460ace033ae5cad86699 --- /dev/null +++ b/core/modules/package_manager/package_manager.routing.yml @@ -0,0 +1,6 @@ +package_manager.settings: + path: '/admin/config/development/package-manager' + defaults: + _form: 'Drupal\package_manager\Form\SettingsForm' + requirements: + _permission: 'administer software updates' diff --git a/core/modules/package_manager/package_manager.services.yml b/core/modules/package_manager/package_manager.services.yml index 88a837631012faf1c718d76ca80a1a5a555204c4..2d1cc482ad89d48028d088413ca31f8b336df344 100644 --- a/core/modules/package_manager/package_manager.services.yml +++ b/core/modules/package_manager/package_manager.services.yml @@ -43,6 +43,7 @@ services: arguments: $appRoot: '%app.root%' Drupal\package_manager\FailureMarker: {} + Drupal\package_manager\EventSubscriber\ConfigSubscriber: {} Drupal\package_manager\EventSubscriber\UpdateDataSubscriber: {} Drupal\package_manager\EventSubscriber\ChangeLogger: calls: diff --git a/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php b/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..0155ded188837b7ce5fec89d03bd4fe335500385 --- /dev/null +++ b/core/modules/package_manager/src/EventSubscriber/ConfigSubscriber.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\package_manager\EventSubscriber; + +use Drupal\Core\Config\ConfigEvents; +use Drupal\Core\Config\StorageTransformEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Event subscriber to remove executable paths from exported config. + * + * @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 ConfigSubscriber implements EventSubscriberInterface { + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + ConfigEvents::STORAGE_TRANSFORM_EXPORT => 'onExport', + ]; + } + + /** + * Removes executable paths from exported config. + * + * @param \Drupal\Core\Config\StorageTransformEvent $event + * The event object. + */ + public function onExport(StorageTransformEvent $event): void { + $storage = $event->getStorage(); + + $settings = $storage->read('package_manager.settings'); + $settings['executables'] = [ + 'composer' => NULL, + 'rsync' => NULL, + ]; + $storage->write('package_manager.settings', $settings); + } + +} diff --git a/core/modules/package_manager/src/Form/SettingsForm.php b/core/modules/package_manager/src/Form/SettingsForm.php new file mode 100644 index 0000000000000000000000000000000000000000..0b690c70cd95cd0ead4216cba1216e2062915419 --- /dev/null +++ b/core/modules/package_manager/src/Form/SettingsForm.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\package_manager\Form; + +use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\ConfigTarget; +use Drupal\Core\Form\FormStateInterface; + +/** + * Defines a form to configure Package Manager settings. + * + * @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 SettingsForm extends ConfigFormBase { + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'package_manager_settings_form'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames(): array { + return ['package_manager.settings']; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + $value_or_null = fn (string $value): ?string => trim($value) ?: NULL; + + $form['executables']['composer'] = [ + '#type' => 'textfield', + '#title' => $this->t('Path to Composer'), + '#description' => $this->t('The full path to the Composer executable (usually named <code>composer</code> or <code>composer.phar</code>. Leave blank to auto-detect.'), + '#config_target' => new ConfigTarget( + 'package_manager.settings', + 'executables.composer', + toConfig: $value_or_null, + ), + ]; + $form['executables']['rsync'] = [ + '#type' => 'textfield', + '#title' => $this->t('Path to rsync'), + '#description' => $this->t('The full path to the <code>rsync</code> executable. Leave blank to auto-detect.'), + '#config_target' => new ConfigTarget( + 'package_manager.settings', + 'executables.rsync', + toConfig: $value_or_null, + ), + ]; + return parent::buildForm($form, $form_state); + } + +} diff --git a/core/modules/package_manager/tests/src/Kernel/ConfigTest.php b/core/modules/package_manager/tests/src/Kernel/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6e7b97c2bfad6bc107405428a14a443be57b5e3c --- /dev/null +++ b/core/modules/package_manager/tests/src/Kernel/ConfigTest.php @@ -0,0 +1,59 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\package_manager\Kernel; + +use Drupal\Core\Config\StorageManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface; + +/** + * @group package_manager + * @internal + */ +class ConfigTest extends PackageManagerKernelTestBase { + + /** + * Tests that Package Manager auto-detects executable paths on install. + */ + public function testExecutablePathsAutoDetection(): void { + $this->container->set(ExecutableFinderInterface::class, new class () implements ExecutableFinderInterface { + + /** + * {@inheritdoc} + */ + public function find(string $name): string { + return match ($name) { + 'composer' => '/fake/path/to/composer', + 'rsync' => '/fake/path/to/rsync', + }; + } + + }); + + $executables = $this->config('package_manager.settings') + ->get('executables'); + $this->assertNull($executables['composer']); + $this->assertNull($executables['rsync']); + + $this->container->get(ModuleHandlerInterface::class) + ->loadInclude('package_manager', 'install'); + package_manager_install(); + + $executables = $this->config('package_manager.settings') + ->get('executables'); + $this->assertSame('/fake/path/to/composer', $executables['composer']); + $this->assertSame('/fake/path/to/rsync', $executables['rsync']); + + // The executable paths should be removed from exported config. + /** @var \Drupal\Core\Config\StorageInterface $export */ + $export = $this->container->get(StorageManagerInterface::class) + ->getStorage(); + $exported_settings = $export->read('package_manager.settings'); + $this->assertIsArray($exported_settings); + $this->assertNull($exported_settings['executables']['composer']); + $this->assertNull($exported_settings['executables']['rsync']); + } + +} diff --git a/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php b/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php index fcdf72d1b8c33798072556fc31131a22dc9aaaf6..13946e62a624c9f806c9c2801d339837aaf07783 100644 --- a/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php +++ b/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php @@ -43,6 +43,7 @@ class DefaultConfigTest extends KernelTestBase { */ public static $skippedConfig = [ 'locale.settings' => ['path: '], + 'package_manager.settings' => ['composer: ', 'rsync: '], 'syslog.settings' => ['facility: '], ];