Skip to content
Snippets Groups Projects
Commit f28f226c authored by Ted Bowman's avatar Ted Bowman
Browse files

Merge branch '3382942-round-3' into '3.0.x'

Issue #3382942: Eliminate dependencies on Composer Stager internals

See merge request !951
parents afc2e25c 4773624d
No related branches found
No related tags found
No related merge requests found
Pipeline #17479 skipped
Showing
with 160 additions and 160 deletions
...@@ -10,20 +10,23 @@ services: ...@@ -10,20 +10,23 @@ services:
# Basic infrastructure services for Composer Stager, overridden by us to # Basic infrastructure services for Composer Stager, overridden by us to
# provide additional functionality. # provide additional functionality.
Drupal\package_manager\ProcessFactory:
public: false
Drupal\package_manager\ExecutableFinder: Drupal\package_manager\ExecutableFinder:
public: false public: false
decorates: 'PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface'
Drupal\package_manager\FileSyncerFactory:
public: false
decorates: 'PhpTuf\ComposerStager\API\FileSyncer\Factory\FileSyncerFactoryInterface'
Drupal\package_manager\ProcessFactory:
public: false
decorates: 'PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface'
Drupal\package_manager\TranslatableStringFactory: Drupal\package_manager\TranslatableStringFactory:
public: false public: false
PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface: decorates: 'PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface'
alias: 'Drupal\package_manager\ExecutableFinder' Drupal\package_manager\NoSymlinksPointToADirectory:
PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface: public: false
alias: 'Drupal\package_manager\ProcessFactory' decorates: 'PhpTuf\ComposerStager\API\Precondition\Service\NoSymlinksPointToADirectoryInterface'
PhpTuf\ComposerStager\API\FileSyncer\Service\FileSyncerInterface: PhpTuf\ComposerStager\API\FileSyncer\Service\FileSyncerInterface:
factory: ['@Drupal\package_manager\FileSyncerFactory', 'create'] factory: ['@PhpTuf\ComposerStager\API\FileSyncer\Factory\FileSyncerFactoryInterface', 'create']
PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface:
alias: 'Drupal\package_manager\TranslatableStringFactory'
logger.channel.package_manager: logger.channel.package_manager:
parent: logger.channel_base parent: logger.channel_base
arguments: arguments:
...@@ -34,15 +37,9 @@ services: ...@@ -34,15 +37,9 @@ services:
- 'package_manager_change_log' - 'package_manager_change_log'
# Services provided to Drupal by Package Manager. # Services provided to Drupal by Package Manager.
package_manager.beginner: package_manager.beginner: '@PhpTuf\ComposerStager\API\Core\BeginnerInterface'
class: PhpTuf\ComposerStager\Internal\Core\Beginner package_manager.stager: '@PhpTuf\ComposerStager\API\Core\StagerInterface'
PhpTuf\ComposerStager\API\Core\BeginnerInterface: '@package_manager.beginner' package_manager.committer: '@PhpTuf\ComposerStager\API\Core\CommitterInterface'
package_manager.stager:
class: PhpTuf\ComposerStager\Internal\Core\Stager
PhpTuf\ComposerStager\API\Core\StagerInterface: '@package_manager.stager'
package_manager.committer:
class: PhpTuf\ComposerStager\Internal\Core\Committer
PhpTuf\ComposerStager\API\Core\CommitterInterface: '@package_manager.committer'
package_manager.path_locator: package_manager.path_locator:
class: Drupal\package_manager\PathLocator class: Drupal\package_manager\PathLocator
arguments: arguments:
......
...@@ -7,8 +7,8 @@ namespace Drupal\package_manager\Event; ...@@ -7,8 +7,8 @@ namespace Drupal\package_manager\Event;
use Drupal\package_manager\StageBase; use Drupal\package_manager\StageBase;
use Drupal\package_manager\PathLocator; use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface; use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface;
use PhpTuf\ComposerStager\API\Path\Factory\PathListFactoryInterface;
use PhpTuf\ComposerStager\API\Path\Value\PathListInterface; use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
use PhpTuf\ComposerStager\Internal\Path\Value\PathList;
/** /**
* Defines an event that collects paths to exclude. * Defines an event that collects paths to exclude.
...@@ -18,13 +18,6 @@ use PhpTuf\ComposerStager\Internal\Path\Value\PathList; ...@@ -18,13 +18,6 @@ use PhpTuf\ComposerStager\Internal\Path\Value\PathList;
*/ */
final class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface { final class CollectPathsToExcludeEvent extends StageEvent implements PathListInterface {
/**
* The list of paths to exclude.
*
* @var \PhpTuf\ComposerStager\API\Path\Value\PathListInterface
*/
protected PathListInterface $pathList;
/** /**
* Constructs a CollectPathsToExcludeEvent object. * Constructs a CollectPathsToExcludeEvent object.
* *
...@@ -34,14 +27,19 @@ final class CollectPathsToExcludeEvent extends StageEvent implements PathListInt ...@@ -34,14 +27,19 @@ final class CollectPathsToExcludeEvent extends StageEvent implements PathListInt
* The path locator service. * The path locator service.
* @param \PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface $pathFactory * @param \PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface $pathFactory
* The path factory service. * The path factory service.
* @param \PhpTuf\ComposerStager\API\Path\Value\PathListInterface|null $pathList
* (optional) The list of paths to exclude.
*/ */
public function __construct( public function __construct(
StageBase $stage, StageBase $stage,
private readonly PathLocator $pathLocator, private readonly PathLocator $pathLocator,
private readonly PathFactoryInterface $pathFactory private readonly PathFactoryInterface $pathFactory,
private ?PathListInterface $pathList = NULL,
) { ) {
parent::__construct($stage); parent::__construct($stage);
$this->pathList = new PathList();
$this->pathList ??= \Drupal::service(PathListFactoryInterface::class)
->create();
} }
/** /**
...@@ -55,7 +53,7 @@ final class CollectPathsToExcludeEvent extends StageEvent implements PathListInt ...@@ -55,7 +53,7 @@ final class CollectPathsToExcludeEvent extends StageEvent implements PathListInt
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getAll(): array { public function getAll(): array {
return $this->pathList->getAll(); return array_unique($this->pathList->getAll());
} }
/** /**
......
...@@ -4,7 +4,9 @@ declare(strict_types = 1); ...@@ -4,7 +4,9 @@ declare(strict_types = 1);
namespace Drupal\package_manager\Event; namespace Drupal\package_manager\Event;
use Drupal\package_manager\ImmutablePathList;
use Drupal\package_manager\StageBase; use Drupal\package_manager\StageBase;
use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
/** /**
* Event fired before staged changes are synced to the active directory. * Event fired before staged changes are synced to the active directory.
...@@ -12,36 +14,26 @@ use Drupal\package_manager\StageBase; ...@@ -12,36 +14,26 @@ use Drupal\package_manager\StageBase;
final class PreApplyEvent extends PreOperationStageEvent { final class PreApplyEvent extends PreOperationStageEvent {
/** /**
* Paths to exclude from the update. * The list of paths to ignore in the active and stage directories.
* *
* @var string[] * @var \Drupal\package_manager\ImmutablePathList
*/ */
private array $excludedPaths = []; public readonly ImmutablePathList $excludedPaths;
/**
* Returns the paths to exclude from the current operation.
*
* @return string[]
* The paths to exclude.
*/
public function getExcludedPaths(): array {
return array_unique($this->excludedPaths);
}
/** /**
* Constructs a PreApplyEvent object. * Constructs a PreApplyEvent object.
* *
* @param \Drupal\package_manager\StageBase $stage * @param \Drupal\package_manager\StageBase $stage
* The stage which fired this event. * The stage which fired this event.
* @param string[] $paths_to_exclude * @param \PhpTuf\ComposerStager\API\Path\Value\PathListInterface $excluded_paths
* The list of paths to exclude. These will not be copied from the stage * The list of paths to exclude. These will not be copied from the stage
* directory to the active directory, nor be deleted from the active * directory to the active directory, nor be deleted from the active
* directory if they exist, when the stage directory is copied back into * directory if they exist, when the stage directory is copied back into
* the active directory. * the active directory.
*/ */
public function __construct(StageBase $stage, array $paths_to_exclude) { public function __construct(StageBase $stage, PathListInterface $excluded_paths) {
parent::__construct($stage); parent::__construct($stage);
$this->excludedPaths = $paths_to_exclude; $this->excludedPaths = new ImmutablePathList($excluded_paths);
} }
} }
...@@ -4,7 +4,9 @@ declare(strict_types = 1); ...@@ -4,7 +4,9 @@ declare(strict_types = 1);
namespace Drupal\package_manager\Event; namespace Drupal\package_manager\Event;
use Drupal\package_manager\ImmutablePathList;
use Drupal\package_manager\StageBase; use Drupal\package_manager\StageBase;
use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
/** /**
* Event fired before a stage directory is created. * Event fired before a stage directory is created.
...@@ -12,34 +14,24 @@ use Drupal\package_manager\StageBase; ...@@ -12,34 +14,24 @@ use Drupal\package_manager\StageBase;
final class PreCreateEvent extends PreOperationStageEvent { final class PreCreateEvent extends PreOperationStageEvent {
/** /**
* Paths to exclude from the update. * The list of paths to exclude from the stage directory.
* *
* @var string[] * @var \Drupal\package_manager\ImmutablePathList
*/ */
private array $excludedPaths = []; public readonly ImmutablePathList $excludedPaths;
/**
* Returns the paths to exclude from the current operation.
*
* @return string[]
* The paths to exclude.
*/
public function getExcludedPaths(): array {
return array_unique($this->excludedPaths);
}
/** /**
* Constructs a PreCreateEvent object. * Constructs a PreCreateEvent object.
* *
* @param \Drupal\package_manager\StageBase $stage * @param \Drupal\package_manager\StageBase $stage
* The stage which fired this event. * The stage which fired this event.
* @param string[] $paths_to_exclude * @param \PhpTuf\ComposerStager\API\Path\Value\PathListInterface $excluded_paths
* The list of paths to exclude. These will not be copied into the stage * The list of paths to exclude. These will not be copied into the stage
* directory when it is created. * directory when it is created.
*/ */
public function __construct(StageBase $stage, array $paths_to_exclude) { public function __construct(StageBase $stage, PathListInterface $excluded_paths) {
parent::__construct($stage); parent::__construct($stage);
$this->excludedPaths = $paths_to_exclude; $this->excludedPaths = new ImmutablePathList($excluded_paths);
} }
} }
...@@ -5,9 +5,10 @@ declare(strict_types = 1); ...@@ -5,9 +5,10 @@ declare(strict_types = 1);
namespace Drupal\package_manager\Event; namespace Drupal\package_manager\Event;
use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\package_manager\ImmutablePathList;
use Drupal\package_manager\StageBase; use Drupal\package_manager\StageBase;
use Drupal\package_manager\ValidationResult; use Drupal\package_manager\ValidationResult;
use Drupal\system\SystemManager; use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
/** /**
* Event fired to check the status of the system to use Package Manager. * Event fired to check the status of the system to use Package Manager.
...@@ -18,36 +19,38 @@ use Drupal\system\SystemManager; ...@@ -18,36 +19,38 @@ use Drupal\system\SystemManager;
final class StatusCheckEvent extends PreOperationStageEvent { final class StatusCheckEvent extends PreOperationStageEvent {
/** /**
* Returns paths to exclude or NULL if a base requirement is not fulfilled. * The paths to exclude, or NULL if there was an error collecting them.
* *
* @return string[]|null * @var \Drupal\package_manager\ImmutablePathList|null
* The paths to exclude, or NULL if a base requirement is not fulfilled.
* *
* @throws \LogicException * @see ::__construct()
* Thrown if the excluded paths are NULL and no errors have been added to
* this event.
*/ */
public function getExcludedPaths(): ?array { public readonly ?ImmutablePathList $excludedPaths;
if (isset($this->pathsToExclude)) {
return array_unique($this->pathsToExclude);
}
if (empty($this->getResults(SystemManager::REQUIREMENT_ERROR))) {
throw new \LogicException('$paths_to_exclude should only be NULL if the error that caused the paths to not be collected was added to the status check event.');
}
return NULL;
}
/** /**
* Constructs a StatusCheckEvent object. * Constructs a StatusCheckEvent object.
* *
* @param \Drupal\package_manager\StageBase $stage * @param \Drupal\package_manager\StageBase $stage
* The stage which fired this event. * The stage which fired this event.
* @param string[]|null $pathsToExclude * @param \PhpTuf\ComposerStager\API\Path\Value\PathListInterface|\Throwable $excluded_paths
* The list of paths to exclude, or NULL if they could not be collected. * The list of paths to exclude or, if an error occurred while they were
* being collected, the throwable from that error. If this is a throwable,
* it will be converted to a validation error.
*/ */
public function __construct(StageBase $stage, private ?array $pathsToExclude) { public function __construct(StageBase $stage, PathListInterface|\Throwable $excluded_paths) {
parent::__construct($stage); parent::__construct($stage);
// If there was an error collecting the excluded paths, convert it to a
// validation error so we can still run status checks that don't need to
// examine the list of excluded paths.
if ($excluded_paths instanceof \Throwable) {
$this->addErrorFromThrowable($excluded_paths);
$excluded_paths = NULL;
}
else {
$excluded_paths = new ImmutablePathList($excluded_paths);
}
$this->excludedPaths = $excluded_paths;
} }
/** /**
......
...@@ -6,7 +6,6 @@ namespace Drupal\package_manager; ...@@ -6,7 +6,6 @@ namespace Drupal\package_manager;
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface; use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
use PhpTuf\ComposerStager\Internal\Finder\Service\ExecutableFinder as StagerExecutableFinder;
/** /**
* An executable finder which looks for executable paths in configuration. * An executable finder which looks for executable paths in configuration.
...@@ -21,13 +20,13 @@ final class ExecutableFinder implements ExecutableFinderInterface { ...@@ -21,13 +20,13 @@ final class ExecutableFinder implements ExecutableFinderInterface {
/** /**
* Constructs an ExecutableFinder object. * Constructs an ExecutableFinder object.
* *
* @param \PhpTuf\ComposerStager\Internal\Finder\Service\ExecutableFinder $decorated * @param \PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface $decorated
* The decorated executable finder. * The decorated executable finder.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory service. * The config factory service.
*/ */
public function __construct( public function __construct(
private readonly StagerExecutableFinder $decorated, private readonly ExecutableFinderInterface $decorated,
private readonly ConfigFactoryInterface $configFactory private readonly ConfigFactoryInterface $configFactory
) {} ) {}
......
...@@ -18,7 +18,7 @@ use PhpTuf\ComposerStager\API\FileSyncer\Service\RsyncFileSyncerInterface; ...@@ -18,7 +18,7 @@ use PhpTuf\ComposerStager\API\FileSyncer\Service\RsyncFileSyncerInterface;
* at any time without warning. External code should not interact with this * at any time without warning. External code should not interact with this
* class. * class.
*/ */
final class FileSyncerFactory { final class FileSyncerFactory implements FileSyncerFactoryInterface {
/** /**
* Constructs a FileSyncerFactory object. * Constructs a FileSyncerFactory object.
......
<?php
declare(strict_types = 1);
namespace Drupal\package_manager;
use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
/**
* Defines a path list that cannot be changed.
*
* @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 ImmutablePathList implements PathListInterface {
/**
* Constructs an ImmutablePathList object.
*
* @param \PhpTuf\ComposerStager\API\Path\Value\PathListInterface $decorated
* The decorated path list.
*/
public function __construct(private readonly PathListInterface $decorated) {}
/**
* {@inheritdoc}
*/
public function add(string ...$paths): never {
throw new \LogicException('Immutable path lists cannot be changed.');
}
/**
* {@inheritdoc}
*/
public function getAll(): array {
return $this->decorated->getAll();
}
}
...@@ -7,8 +7,6 @@ namespace Drupal\package_manager; ...@@ -7,8 +7,6 @@ namespace Drupal\package_manager;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase; use Drupal\Core\DependencyInjection\ServiceProviderBase;
use PhpTuf\ComposerStager\API\Core\BeginnerInterface; use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
use PhpTuf\ComposerStager\API\FileSyncer\Factory\FileSyncerFactoryInterface;
use PhpTuf\ComposerStager\API\Precondition\Service\NoSymlinksPointToADirectoryInterface;
/** /**
* Defines dynamic container services for Package Manager. * Defines dynamic container services for Package Manager.
...@@ -121,16 +119,6 @@ final class PackageManagerServiceProvider extends ServiceProviderBase { ...@@ -121,16 +119,6 @@ final class PackageManagerServiceProvider extends ServiceProviderBase {
} }
} }
// END: DELETE FROM CORE MERGE REQUEST // END: DELETE FROM CORE MERGE REQUEST
// Decorate certain Composer Stager preconditions.
$container->register(NoSymlinksPointToADirectory::class)
->setPublic(FALSE)
->setAutowired(TRUE)
->setDecoratedService(NoSymlinksPointToADirectoryInterface::class);
$container->register(FileSyncerFactory::class)
->setPublic(FALSE)
->setAutowired(TRUE)
->setDecoratedService(FileSyncerFactoryInterface::class);
} }
} }
...@@ -8,7 +8,6 @@ use Drupal\Core\Config\ConfigFactoryInterface; ...@@ -8,7 +8,6 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface; use Drupal\Core\File\FileSystemInterface;
use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface; use PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface;
use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface; use PhpTuf\ComposerStager\API\Process\Service\ProcessInterface;
use PhpTuf\ComposerStager\Internal\Process\Factory\ProcessFactory as StagerProcessFactory;
// cspell:ignore BINDIR // cspell:ignore BINDIR
...@@ -29,13 +28,13 @@ final class ProcessFactory implements ProcessFactoryInterface { ...@@ -29,13 +28,13 @@ final class ProcessFactory implements ProcessFactoryInterface {
* The file system service. * The file system service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory service. * The config factory service.
* @param \PhpTuf\ComposerStager\Internal\Process\Factory\ProcessFactory $decorated * @param \PhpTuf\ComposerStager\API\Process\Factory\ProcessFactoryInterface $decorated
* The decorated process factory service. * The decorated process factory service.
*/ */
public function __construct( public function __construct(
private readonly FileSystemInterface $fileSystem, private readonly FileSystemInterface $fileSystem,
private readonly ConfigFactoryInterface $configFactory, private readonly ConfigFactoryInterface $configFactory,
private readonly StagerProcessFactory $decorated, private readonly ProcessFactoryInterface $decorated,
) {} ) {}
/** /**
......
...@@ -34,7 +34,7 @@ use PhpTuf\ComposerStager\API\Core\StagerInterface; ...@@ -34,7 +34,7 @@ use PhpTuf\ComposerStager\API\Core\StagerInterface;
use PhpTuf\ComposerStager\API\Exception\InvalidArgumentException; use PhpTuf\ComposerStager\API\Exception\InvalidArgumentException;
use PhpTuf\ComposerStager\API\Exception\PreconditionException; use PhpTuf\ComposerStager\API\Exception\PreconditionException;
use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface; use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface;
use PhpTuf\ComposerStager\Internal\Path\Value\PathList; use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
...@@ -254,7 +254,7 @@ abstract class StageBase implements LoggerAwareInterface { ...@@ -254,7 +254,7 @@ abstract class StageBase implements LoggerAwareInterface {
/** /**
* Collects paths that Composer Stager should exclude. * Collects paths that Composer Stager should exclude.
* *
* @return string[] * @return \PhpTuf\ComposerStager\API\Path\Value\PathListInterface
* A list of paths that Composer Stager should exclude when creating the * A list of paths that Composer Stager should exclude when creating the
* stage directory and applying staged changes to the active directory. * stage directory and applying staged changes to the active directory.
* *
...@@ -264,15 +264,14 @@ abstract class StageBase implements LoggerAwareInterface { ...@@ -264,15 +264,14 @@ abstract class StageBase implements LoggerAwareInterface {
* @see ::create() * @see ::create()
* @see ::apply() * @see ::apply()
*/ */
protected function getPathsToExclude(): array { protected function getPathsToExclude(): PathListInterface {
$event = new CollectPathsToExcludeEvent($this, $this->pathLocator, $this->pathFactory); $event = new CollectPathsToExcludeEvent($this, $this->pathLocator, $this->pathFactory);
try { try {
$this->eventDispatcher->dispatch($event); return $this->eventDispatcher->dispatch($event);
} }
catch (\Throwable $e) { catch (\Throwable $e) {
$this->rethrowAsStageException($e); $this->rethrowAsStageException($e);
} }
return $event->getAll();
} }
/** /**
...@@ -324,13 +323,14 @@ abstract class StageBase implements LoggerAwareInterface { ...@@ -324,13 +323,14 @@ abstract class StageBase implements LoggerAwareInterface {
$active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot()); $active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot());
$stage_dir = $this->pathFactory->create($this->getStageDirectory()); $stage_dir = $this->pathFactory->create($this->getStageDirectory());
$event = new PreCreateEvent($this, $this->getPathsToExclude()); $excluded_paths = $this->getPathsToExclude();
$event = new PreCreateEvent($this, $excluded_paths);
// If an error occurs and we won't be able to create the stage, mark it as // If an error occurs and we won't be able to create the stage, mark it as
// available. // available.
$this->dispatch($event, [$this, 'markAsAvailable']); $this->dispatch($event, [$this, 'markAsAvailable']);
try { try {
$this->beginner->begin($active_dir, $stage_dir, new PathList(...$event->getExcludedPaths()), NULL, $timeout); $this->beginner->begin($active_dir, $stage_dir, $excluded_paths, NULL, $timeout);
} }
catch (\Throwable $error) { catch (\Throwable $error) {
$this->destroy(); $this->destroy();
...@@ -442,23 +442,24 @@ abstract class StageBase implements LoggerAwareInterface { ...@@ -442,23 +442,24 @@ abstract class StageBase implements LoggerAwareInterface {
$active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot()); $active_dir = $this->pathFactory->create($this->pathLocator->getProjectRoot());
$stage_dir = $this->pathFactory->create($this->getStageDirectory()); $stage_dir = $this->pathFactory->create($this->getStageDirectory());
$excluded_paths = $this->getPathsToExclude();
// Exclude the failure file from the commit operation.
$excluded_paths->add(
str_replace($active_dir->absolute() . DIRECTORY_SEPARATOR, '', $this->failureMarker->getPath()),
);
// If an error occurs while dispatching the events, ensure that ::destroy() // If an error occurs while dispatching the events, ensure that ::destroy()
// doesn't think we're in the middle of applying the staged changes to the // doesn't think we're in the middle of applying the staged changes to the
// active directory. // active directory.
$event = new PreApplyEvent($this, $this->getPathsToExclude()); $event = new PreApplyEvent($this, $excluded_paths);
$this->tempStore->set(self::TEMPSTORE_APPLY_TIME_KEY, $this->time->getRequestTime()); $this->tempStore->set(self::TEMPSTORE_APPLY_TIME_KEY, $this->time->getRequestTime());
$this->dispatch($event, $this->setNotApplying(...)); $this->dispatch($event, $this->setNotApplying(...));
// Create a marker file so that we can tell later on if the commit failed. // Create a marker file so that we can tell later on if the commit failed.
$this->failureMarker->write($this, $this->getFailureMarkerMessage()); $this->failureMarker->write($this, $this->getFailureMarkerMessage());
// Exclude the failure file from the commit operation.
$paths_to_exclude = new PathList(...$event->getExcludedPaths());
$paths_to_exclude->add(
str_replace($this->pathLocator->getProjectRoot() . DIRECTORY_SEPARATOR, '', $this->failureMarker->getPath()),
);
try { try {
$this->committer->commit($stage_dir, $active_dir, $paths_to_exclude, NULL, $timeout); $this->committer->commit($stage_dir, $active_dir, $excluded_paths, NULL, $timeout);
} }
catch (InvalidArgumentException | PreconditionException $e) { catch (InvalidArgumentException | PreconditionException $e) {
// The commit operation has not started yet, so we can clear the failure // The commit operation has not started yet, so we can clear the failure
......
...@@ -42,22 +42,12 @@ trait StatusCheckTrait { ...@@ -42,22 +42,12 @@ trait StatusCheckTrait {
try { try {
$paths_to_exclude_event = new CollectPathsToExcludeEvent($stage, $path_locator, $path_factory); $paths_to_exclude_event = new CollectPathsToExcludeEvent($stage, $path_locator, $path_factory);
$event_dispatcher->dispatch($paths_to_exclude_event); $event_dispatcher->dispatch($paths_to_exclude_event);
$event = new StatusCheckEvent($stage, $paths_to_exclude_event->getAll());
} }
catch (\Throwable $throwable) { catch (\Throwable $throwable) {
// We can dispatch the status check event without the paths to exclude, $paths_to_exclude_event = $throwable;
// but it must be set explicitly to NULL, to allow those status checks to
// run that do not need the paths to exclude.
$event = new StatusCheckEvent($stage, NULL);
// Add the error that was encountered so that regardless of any other
// validation errors BaseRequirementsFulfilledValidator will stop the
// event propagation after the base requirement validators have run.
// @see \Drupal\package_manager\Validator\BaseRequirementsFulfilledValidator
$event->addErrorFromThrowable($throwable, t('Unable to collect the paths to exclude.'));
} }
$event = new StatusCheckEvent($stage, $paths_to_exclude_event);
$event_dispatcher->dispatch($event); return $event_dispatcher->dispatch($event)->getResults();
return $event->getResults();
} }
} }
...@@ -9,7 +9,6 @@ use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface; ...@@ -9,7 +9,6 @@ use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
use PhpTuf\ComposerStager\API\Translation\Service\DomainOptionsInterface; use PhpTuf\ComposerStager\API\Translation\Service\DomainOptionsInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslatableInterface; use PhpTuf\ComposerStager\API\Translation\Value\TranslatableInterface;
use PhpTuf\ComposerStager\API\Translation\Value\TranslationParametersInterface; use PhpTuf\ComposerStager\API\Translation\Value\TranslationParametersInterface;
use PhpTuf\ComposerStager\Internal\Translation\Factory\TranslatableFactory as StagerTranslatableFactory;
/** /**
* Creates translatable strings that can interoperate with Composer Stager. * Creates translatable strings that can interoperate with Composer Stager.
...@@ -24,13 +23,13 @@ final class TranslatableStringFactory implements TranslatableFactoryInterface { ...@@ -24,13 +23,13 @@ final class TranslatableStringFactory implements TranslatableFactoryInterface {
/** /**
* Constructs a TranslatableStringFactory object. * Constructs a TranslatableStringFactory object.
* *
* @param \PhpTuf\ComposerStager\Internal\Translation\Factory\TranslatableFactory $decorated * @param \PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface $decorated
* The decorated translatable factory service. * The decorated translatable factory service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The string translation service. * The string translation service.
*/ */
public function __construct( public function __construct(
private readonly StagerTranslatableFactory $decorated, private readonly TranslatableFactoryInterface $decorated,
private readonly TranslationInterface $translation, private readonly TranslationInterface $translation,
) {} ) {}
......
...@@ -9,7 +9,7 @@ use Drupal\package_manager\Event\PreRequireEvent; ...@@ -9,7 +9,7 @@ use Drupal\package_manager\Event\PreRequireEvent;
use Drupal\package_manager\PathLocator; use Drupal\package_manager\PathLocator;
use PhpTuf\ComposerStager\API\Exception\PreconditionException; use PhpTuf\ComposerStager\API\Exception\PreconditionException;
use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface; use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface;
use PhpTuf\ComposerStager\Internal\Path\Value\PathList; use PhpTuf\ComposerStager\API\Path\Factory\PathListFactoryInterface;
use PhpTuf\ComposerStager\API\Precondition\Service\NoUnsupportedLinksExistInterface; use PhpTuf\ComposerStager\API\Precondition\Service\NoUnsupportedLinksExistInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
...@@ -36,11 +36,14 @@ final class SymlinkValidator implements EventSubscriberInterface { ...@@ -36,11 +36,14 @@ final class SymlinkValidator implements EventSubscriberInterface {
* The Composer Stager precondition that this validator wraps. * The Composer Stager precondition that this validator wraps.
* @param \PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface $pathFactory * @param \PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface $pathFactory
* The path factory service. * The path factory service.
* @param \PhpTuf\ComposerStager\API\Path\Factory\PathListFactoryInterface $pathListFactory
* The path list factory service.
*/ */
public function __construct( public function __construct(
private readonly PathLocator $pathLocator, private readonly PathLocator $pathLocator,
private readonly NoUnsupportedLinksExistInterface $precondition, private readonly NoUnsupportedLinksExistInterface $precondition,
private readonly PathFactoryInterface $pathFactory, private readonly PathFactoryInterface $pathFactory,
private readonly PathListFactoryInterface $pathListFactory,
) {} ) {}
/** /**
...@@ -65,17 +68,20 @@ final class SymlinkValidator implements EventSubscriberInterface { ...@@ -65,17 +68,20 @@ final class SymlinkValidator implements EventSubscriberInterface {
} }
$stage_dir = $this->pathFactory->create($stage_dir); $stage_dir = $this->pathFactory->create($stage_dir);
$excluded_paths = $event->getExcludedPaths();
// Return early if no excluded paths were collected because this validator // Return early if no excluded paths were collected because this validator
// is dependent on knowing which paths to exclude when searching for // is dependent on knowing which paths to exclude when searching for
// symlinks. // symlinks.
// @see \Drupal\package_manager\StatusCheckTrait::runStatusCheck() // @see \Drupal\package_manager\StatusCheckTrait::runStatusCheck()
if ($excluded_paths === NULL) { if ($event->excludedPaths === NULL) {
return; return;
} }
// The list of excluded paths is immutable, but the precondition may need to
// mutate it, so convert it back to a normal, mutable path list.
$exclusions = $this->pathListFactory->create(...$event->excludedPaths->getAll());
try { try {
$this->precondition->assertIsFulfilled($active_dir, $stage_dir, new PathList(...$excluded_paths)); $this->precondition->assertIsFulfilled($active_dir, $stage_dir, $exclusions);
} }
catch (PreconditionException $e) { catch (PreconditionException $e) {
$event->addErrorFromThrowable($e); $event->addErrorFromThrowable($e);
......
services: services:
fixture_manipulator.stage_manipulator: _defaults:
class: Drupal\fixture_manipulator\StageFixtureManipulator autowire: true
arguments: ['@state', '@fixture_manipulator.stage_manipulator.inner'] Drupal\fixture_manipulator\StageFixtureManipulator:
decorates: 'package_manager.beginner' decorates: 'PhpTuf\ComposerStager\API\Core\BeginnerInterface'
services: services:
package_manager_bypass.beginner: _defaults:
class: Drupal\package_manager_bypass\LoggingBeginner autowire: true
arguments: ['@state', '@package_manager_bypass.beginner.inner' ] Drupal\package_manager_bypass\LoggingBeginner:
decorates: 'package_manager.beginner' decorates: 'PhpTuf\ComposerStager\API\Core\BeginnerInterface'
package_manager_bypass.committer: Drupal\package_manager_bypass\LoggingCommitter:
class: Drupal\package_manager_bypass\LoggingCommitter decorates: 'PhpTuf\ComposerStager\API\Core\CommitterInterface'
arguments: [ '@state', '@package_manager_bypass.committer.inner' ]
decorates: 'package_manager.committer'
...@@ -45,7 +45,7 @@ final class LoggingBeginner implements BeginnerInterface { ...@@ -45,7 +45,7 @@ final class LoggingBeginner implements BeginnerInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function begin(PathInterface $activeDir, PathInterface $stagingDir, ?PathListInterface $exclusions = NULL, ?OutputCallbackInterface $callback = NULL, ?int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void { public function begin(PathInterface $activeDir, PathInterface $stagingDir, ?PathListInterface $exclusions = NULL, ?OutputCallbackInterface $callback = NULL, ?int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void {
$this->saveInvocationArguments($activeDir, $stagingDir, $exclusions, $timeout); $this->saveInvocationArguments($activeDir, $stagingDir, $exclusions?->getAll(), $timeout);
$this->throwExceptionIfSet(); $this->throwExceptionIfSet();
$this->inner->begin($activeDir, $stagingDir, $exclusions, $callback, $timeout); $this->inner->begin($activeDir, $stagingDir, $exclusions, $callback, $timeout);
} }
......
...@@ -45,7 +45,7 @@ final class LoggingCommitter implements CommitterInterface { ...@@ -45,7 +45,7 @@ final class LoggingCommitter implements CommitterInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function commit(PathInterface $stagingDir, PathInterface $activeDir, ?PathListInterface $exclusions = NULL, ?OutputCallbackInterface $callback = NULL, ?int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void { public function commit(PathInterface $stagingDir, PathInterface $activeDir, ?PathListInterface $exclusions = NULL, ?OutputCallbackInterface $callback = NULL, ?int $timeout = ProcessInterface::DEFAULT_TIMEOUT): void {
$this->saveInvocationArguments($stagingDir, $activeDir, $exclusions, $timeout); $this->saveInvocationArguments($stagingDir, $activeDir, $exclusions?->getAll(), $timeout);
$this->throwExceptionIfSet(); $this->throwExceptionIfSet();
$this->inner->commit($stagingDir, $activeDir, $exclusions, $callback, $timeout); $this->inner->commit($stagingDir, $activeDir, $exclusions, $callback, $timeout);
} }
......
...@@ -7,6 +7,7 @@ namespace Drupal\package_manager_bypass; ...@@ -7,6 +7,7 @@ namespace Drupal\package_manager_bypass;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase; use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Site\Settings; use Drupal\Core\Site\Settings;
use PhpTuf\ComposerStager\API\Core\StagerInterface;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
...@@ -23,19 +24,22 @@ final class PackageManagerBypassServiceProvider extends ServiceProviderBase { ...@@ -23,19 +24,22 @@ final class PackageManagerBypassServiceProvider extends ServiceProviderBase {
public function alter(ContainerBuilder $container) { public function alter(ContainerBuilder $container) {
parent::alter($container); parent::alter($container);
$state = new Reference('state');
// By default, \Drupal\package_manager_bypass\NoOpStager is applied, except // By default, \Drupal\package_manager_bypass\NoOpStager is applied, except
// when a test opts out by setting this setting to FALSE. // when a test opts out by setting this setting to FALSE.
// @see \Drupal\package_manager_bypass\NoOpStager::setLockFileShouldChange() // @see \Drupal\package_manager_bypass\NoOpStager::setLockFileShouldChange()
if (Settings::get('package_manager_bypass_composer_stager', TRUE)) { if (Settings::get('package_manager_bypass_composer_stager', TRUE)) {
$container->getDefinition('package_manager.stager')->setClass(NoOpStager::class)->setArguments([$state]); $container->register(NoOpStager::class)
->setClass(NoOpStager::class)
->setPublic(FALSE)
->setAutowired(TRUE)
->setDecoratedService(StagerInterface::class);
} }
$container->getDefinition('package_manager.path_locator') $container->getDefinition('package_manager.path_locator')
->setClass(MockPathLocator::class) ->setClass(MockPathLocator::class)
->setAutowired(FALSE) ->setAutowired(FALSE)
->setArguments([ ->setArguments([
$state, new Reference('state'),
new Parameter('app.root'), new Parameter('app.root'),
new Reference('config.factory'), new Reference('config.factory'),
new Reference('file_system'), new Reference('file_system'),
......
...@@ -7,8 +7,6 @@ namespace Drupal\Tests\package_manager\Kernel; ...@@ -7,8 +7,6 @@ namespace Drupal\Tests\package_manager\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\ExecutableFinder; use Drupal\package_manager\ExecutableFinder;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface; use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
use PhpTuf\ComposerStager\Internal\Finder\Service\ExecutableFinder as StagerExecutableFinder;
use PhpTuf\ComposerStager\API\Translation\Factory\TranslatableFactoryInterface;
use Symfony\Component\Process\ExecutableFinder as SymfonyExecutableFinder; use Symfony\Component\Process\ExecutableFinder as SymfonyExecutableFinder;
/** /**
...@@ -23,7 +21,7 @@ class ExecutableFinderTest extends PackageManagerKernelTestBase { ...@@ -23,7 +21,7 @@ class ExecutableFinderTest extends PackageManagerKernelTestBase {
*/ */
public function register(ContainerBuilder $container) { public function register(ContainerBuilder $container) {
// Mock a Symfony executable finder that always returns /dev/null. // Mock a Symfony executable finder that always returns /dev/null.
$symfony_executable_finder = new class extends SymfonyExecutableFinder { $container->set(SymfonyExecutableFinder::class, new class extends SymfonyExecutableFinder {
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -32,12 +30,7 @@ class ExecutableFinderTest extends PackageManagerKernelTestBase { ...@@ -32,12 +30,7 @@ class ExecutableFinderTest extends PackageManagerKernelTestBase {
return '/dev/null'; return '/dev/null';
} }
}; });
$container->getDefinition(ExecutableFinder::class)
->setArgument('$decorated', new StagerExecutableFinder(
$symfony_executable_finder,
$this->createMock(TranslatableFactoryInterface::class),
));
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment