| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Copyright © Vaimo Group. All rights reserved. |
||
| 4 | * See LICENSE_VAIMO.txt for license details. |
||
| 5 | */ |
||
| 6 | namespace Vaimo\ComposerPatches\Repository; |
||
| 7 | |||
| 8 | use Composer\Repository\WritableRepositoryInterface as Repository; |
||
| 9 | use Composer\Package\PackageInterface as Package; |
||
| 10 | |||
| 11 | use Vaimo\ComposerPatches\Patch\Definition as Patch; |
||
| 12 | |||
| 13 | /** |
||
| 14 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||
| 15 | * @SuppressWarnings(PHPMD.TooManyFields) |
||
| 16 | */ |
||
| 17 | class PatchesApplier |
||
| 18 | { |
||
| 19 | /** |
||
| 20 | * @var \Composer\Composer |
||
| 21 | */ |
||
| 22 | private $composer; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * @var \Vaimo\ComposerPatches\Package\Collector |
||
| 26 | */ |
||
| 27 | private $packageCollector; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * @var \Vaimo\ComposerPatches\Managers\RepositoryManager |
||
| 31 | */ |
||
| 32 | private $repositoryManager; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @var \Vaimo\ComposerPatches\Package\PatchApplier |
||
| 36 | */ |
||
| 37 | private $packagePatchApplier; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @var \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator |
||
| 41 | */ |
||
| 42 | private $queueGenerator; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @var \Vaimo\ComposerPatches\Managers\PatcherStateManager |
||
| 46 | */ |
||
| 47 | private $patcherStateManager; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * @var \Vaimo\ComposerPatches\Repository\StateGenerator |
||
| 51 | */ |
||
| 52 | private $repoStateGenerator; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @var \Vaimo\ComposerPatches\Package\PatchApplier\InfoLogger |
||
| 56 | */ |
||
| 57 | private $patchInfoLogger; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @var \Vaimo\ComposerPatches\Strategies\OutputStrategy |
||
| 61 | */ |
||
| 62 | private $outputStrategy; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * @var \Vaimo\ComposerPatches\Logger |
||
| 66 | */ |
||
| 67 | private $logger; |
||
| 68 | |||
| 69 | /** |
||
| 70 | * @var \Vaimo\ComposerPatches\Patch\DefinitionList\Analyser |
||
| 71 | */ |
||
| 72 | private $patchListAnalyser; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * @var \Vaimo\ComposerPatches\Patch\DefinitionList\Transformer |
||
| 76 | */ |
||
| 77 | private $patchListTransformer; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * @var \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig |
||
| 81 | */ |
||
| 82 | private $statusConfig; |
||
| 83 | |||
| 84 | /** |
||
| 85 | * @var \Vaimo\ComposerPatches\Utils\PackageUtils |
||
| 86 | */ |
||
| 87 | private $packageUtils; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @var \Vaimo\ComposerPatches\Utils\DataUtils |
||
| 91 | */ |
||
| 92 | private $dataUtils; |
||
| 93 | |||
| 94 | /** |
||
| 95 | * @var \Vaimo\ComposerPatches\Console\OutputGenerator |
||
| 96 | */ |
||
| 97 | private $outputGenerator; |
||
| 98 | |||
| 99 | /** |
||
| 100 | * @var \Vaimo\ComposerPatches\Compatibility\Executor |
||
| 101 | */ |
||
| 102 | private $compExecutor; |
||
| 103 | |||
| 104 | public function __construct( |
||
| 105 | \Composer\Composer $composer, |
||
| 106 | \Vaimo\ComposerPatches\Package\Collector $packageCollector, |
||
| 107 | \Vaimo\ComposerPatches\Managers\RepositoryManager $repositoryManager, |
||
| 108 | \Vaimo\ComposerPatches\Package\PatchApplier $patchApplier, |
||
| 109 | \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator $queueGenerator, |
||
| 110 | \Vaimo\ComposerPatches\Managers\PatcherStateManager $patcherStateManager, |
||
| 111 | \Vaimo\ComposerPatches\Package\PatchApplier\InfoLogger $patchInfoLogger, |
||
| 112 | \Vaimo\ComposerPatches\Strategies\OutputStrategy $outputStrategy, |
||
| 113 | \Vaimo\ComposerPatches\Logger $logger |
||
| 114 | ) { |
||
| 115 | $this->composer = $composer; |
||
| 116 | $this->packageCollector = $packageCollector; |
||
| 117 | $this->repositoryManager = $repositoryManager; |
||
| 118 | $this->packagePatchApplier = $patchApplier; |
||
| 119 | $this->queueGenerator = $queueGenerator; |
||
| 120 | $this->patcherStateManager = $patcherStateManager; |
||
| 121 | $this->patchInfoLogger = $patchInfoLogger; |
||
| 122 | $this->outputStrategy = $outputStrategy; |
||
| 123 | $this->logger = $logger; |
||
| 124 | |||
| 125 | $this->repoStateGenerator = new \Vaimo\ComposerPatches\Repository\StateGenerator( |
||
| 126 | $this->packageCollector |
||
| 127 | ); |
||
| 128 | |||
| 129 | $this->outputGenerator = new \Vaimo\ComposerPatches\Console\OutputGenerator($logger); |
||
| 130 | |||
| 131 | $this->patchListAnalyser = new \Vaimo\ComposerPatches\Patch\DefinitionList\Analyser(); |
||
| 132 | $this->patchListTransformer = new \Vaimo\ComposerPatches\Patch\DefinitionList\Transformer(); |
||
| 133 | $this->statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig(); |
||
| 134 | $this->packageUtils = new \Vaimo\ComposerPatches\Utils\PackageUtils(); |
||
| 135 | $this->dataUtils = new \Vaimo\ComposerPatches\Utils\DataUtils(); |
||
| 136 | $this->compExecutor = new \Vaimo\ComposerPatches\Compatibility\Executor(); |
||
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * @SuppressWarnings(PHPMD.ExcessiveMethodLength) |
||
| 141 | * @SuppressWarnings(PHPMD.NPathComplexity) |
||
| 142 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
||
| 143 | * |
||
| 144 | * @param Repository $repository |
||
| 145 | * @param array $patches |
||
| 146 | * @return bool |
||
| 147 | * @throws \Vaimo\ComposerPatches\Exceptions\PackageNotFound |
||
| 148 | * @throws \Vaimo\ComposerPatches\Exceptions\PackageResetException |
||
| 149 | * @throws \Vaimo\ComposerPatches\Exceptions\PatchFailureException |
||
| 150 | */ |
||
| 151 | public function apply(Repository $repository, array $patches) |
||
| 152 | { |
||
| 153 | $packages = $this->packageCollector->collect($repository); |
||
| 154 | $packagesUpdated = false; |
||
| 155 | $repositoryState = $this->repoStateGenerator->generate($repository); |
||
| 156 | $applyQueue = $this->queueGenerator->generateApplyQueue($patches, $repositoryState); |
||
| 157 | $removeQueue = $this->queueGenerator->generateRemovalQueue($applyQueue, $repositoryState); |
||
| 158 | $resetQueue = $this->queueGenerator->generateResetQueue($applyQueue); |
||
| 159 | $applyQueue = array_map('array_filter', $applyQueue); |
||
| 160 | $patchQueueFootprints = $this->patchListTransformer->createSimplifiedList($applyQueue); |
||
| 161 | |||
| 162 | $labels = array_diff_key( |
||
| 163 | $this->statusConfig->getLabels(), |
||
| 164 | array(Patch::STATUS_UNKNOWN => true) |
||
| 165 | ); |
||
| 166 | |||
| 167 | $applyQueue = $this->updateStatusLabels($applyQueue, $labels); |
||
| 168 | $removeQueue = $this->updateStatusLabels($removeQueue, $labels); |
||
| 169 | |||
| 170 | foreach ($packages as $packageName => $package) { |
||
| 171 | $hasPatches = !empty($applyQueue[$packageName]); |
||
| 172 | |||
| 173 | $patchTargets = $hasPatches ? |
||
| 174 | $this->patchListAnalyser->getAllTargets(array($applyQueue[$packageName])) |
||
| 175 | : array($packageName); |
||
| 176 | |||
| 177 | $itemsToReset = $this->dataUtils->extractItems($resetQueue, $patchTargets); |
||
| 178 | |||
| 179 | $resetResult = array(); |
||
| 180 | $resets = array(); |
||
| 181 | |||
| 182 | foreach ($itemsToReset as $targetName) { |
||
| 183 | $resetTarget = $packages[$targetName]; |
||
| 184 | |||
| 185 | $resetPatches = $this->packageUtils->resetAppliedPatches($resetTarget); |
||
| 186 | |||
| 187 | $resetResult[$targetName] = is_array($resetPatches) |
||
| 188 | ? $resetPatches |
||
| 189 | : array(); |
||
| 190 | |||
| 191 | if (!$hasPatches && $resetPatches && !isset($patchQueueFootprints[$targetName])) { |
||
| 192 | $this->logger->writeRaw( |
||
| 193 | 'Resetting patches for <info>%s</info> (%s)', |
||
| 194 | array($targetName, count($resetResult[$targetName])) |
||
| 195 | ); |
||
| 196 | } |
||
| 197 | |||
| 198 | $resets[] = $this->repositoryManager->resetPackage($repository, $resetTarget); |
||
| 199 | } |
||
| 200 | |||
| 201 | $this->compExecutor->waitForCompletion($this->composer, $resets); |
||
| 202 | $packagesUpdated = $packagesUpdated || (bool)array_filter($resetResult); |
||
| 203 | |||
| 204 | if (!$hasPatches) { |
||
| 205 | continue; |
||
| 206 | } |
||
| 207 | |||
| 208 | $changedTargets = $this->resolveChangedTargets($packages, $patchTargets, $patchQueueFootprints); |
||
| 209 | |||
| 210 | if (empty($changedTargets)) { |
||
| 211 | continue; |
||
| 212 | } |
||
| 213 | |||
| 214 | $queuedPatches = array_filter( |
||
| 215 | $applyQueue[$packageName], |
||
| 216 | function ($data) use ($changedTargets) { |
||
| 217 | return array_intersect($data[Patch::TARGETS], $changedTargets); |
||
| 218 | } |
||
| 219 | ); |
||
| 220 | |||
| 221 | $this->updatePackage( |
||
| 222 | $package, |
||
| 223 | $repository, |
||
| 224 | $queuedPatches, |
||
| 225 | $this->dataUtils->extractValue($removeQueue, $packageName, array()) |
||
| 226 | ); |
||
| 227 | |||
| 228 | $packagesUpdated = true; |
||
| 229 | } |
||
| 230 | |||
| 231 | return $packagesUpdated; |
||
| 232 | } |
||
| 233 | |||
| 234 | private function updatePackage(Package $package, Repository $repository, array $additions, array $removals) |
||
| 235 | { |
||
| 236 | $muteDepth = null; |
||
| 237 | |||
| 238 | $packageName = $package->getName(); |
||
| 239 | |||
| 240 | if (!$this->shouldAllowOutput($additions, $removals)) { |
||
| 241 | $muteDepth = $this->logger->mute(); |
||
| 242 | } |
||
| 243 | |||
| 244 | $this->logger->writeRaw( |
||
| 245 | 'Applying patches for <info>%s</info> (%s)', |
||
| 246 | array($packageName, count($additions)) |
||
| 247 | ); |
||
| 248 | |||
| 249 | $this->processQueues($package, $repository, $additions, $removals); |
||
| 250 | |||
| 251 | $this->logger->writeNewLine(); |
||
| 252 | |||
| 253 | if ($muteDepth !== null) { |
||
| 254 | $this->logger->unMute($muteDepth); |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | private function updateStatusLabels(array $queue, array $labels) |
||
| 259 | { |
||
| 260 | foreach ($queue as $target => $group) { |
||
| 261 | foreach ($group as $path => $item) { |
||
| 262 | $status = isset($item[Patch::STATUS]) |
||
| 263 | ? $item[Patch::STATUS] |
||
| 264 | : Patch::STATUS_UNKNOWN; |
||
| 265 | |||
| 266 | if (!isset($labels[$status])) { |
||
| 267 | continue; |
||
| 268 | } |
||
| 269 | |||
| 270 | $queue[$target][$path][Patch::STATUS_LABEL] = $labels[$status]; |
||
| 271 | } |
||
| 272 | } |
||
| 273 | |||
| 274 | return $queue; |
||
| 275 | } |
||
| 276 | |||
| 277 | private function processQueues(Package $package, Repository $repository, $additions, $removals) |
||
| 278 | { |
||
| 279 | try { |
||
| 280 | if ($removals) { |
||
| 281 | $processIndentation = $this->logger->push('~'); |
||
| 282 | |||
| 283 | foreach ($removals as $item) { |
||
| 284 | $this->patchInfoLogger->outputPatchInfo($item); |
||
| 285 | } |
||
| 286 | |||
| 287 | $this->logger->reset($processIndentation); |
||
| 288 | } |
||
| 289 | |||
| 290 | $this->processPatchesForPackage($repository, $package, $additions); |
||
| 291 | } catch (\Exception $exception) { |
||
| 292 | $this->logger->unMute(); |
||
| 293 | |||
| 294 | throw $exception; |
||
| 295 | } |
||
| 296 | } |
||
| 297 | |||
| 298 | private function resolveChangedTargets(array $packages, array $patchTargets, array $patchFootprints) |
||
| 299 | { |
||
| 300 | $changesMap = array(); |
||
| 301 | |||
| 302 | foreach ($patchTargets as $targetName) { |
||
| 303 | $targetQueue = array(); |
||
| 304 | |||
| 305 | if (isset($patchFootprints[$targetName])) { |
||
| 306 | $targetQueue = $patchFootprints[$targetName]; |
||
| 307 | } |
||
| 308 | |||
| 309 | if (!isset($packages[$targetName])) { |
||
| 310 | throw new \Vaimo\ComposerPatches\Exceptions\PackageNotFound( |
||
| 311 | sprintf( |
||
| 312 | 'Unknown target "%s" found when checking patch changes for: %s', |
||
| 313 | $targetName, |
||
| 314 | implode(',', array_keys($targetQueue)) |
||
| 315 | ) |
||
| 316 | ); |
||
| 317 | } |
||
| 318 | |||
| 319 | $changesMap[$targetName] = $this->packageUtils->hasPatchChanges( |
||
| 320 | $packages[$targetName], |
||
| 321 | $targetQueue |
||
| 322 | ); |
||
| 323 | } |
||
| 324 | |||
| 325 | return array_keys(array_filter($changesMap)); |
||
| 326 | } |
||
| 327 | |||
| 328 | private function processPatchesForPackage(Repository $repository, Package $package, array $patchesQueue) |
||
| 329 | { |
||
| 330 | $processIndentation = $this->logger->push('~'); |
||
| 331 | |||
| 332 | try { |
||
| 333 | $appliedPatches = $this->packagePatchApplier->applyPatches($package, $patchesQueue); |
||
| 334 | |||
| 335 | $this->patcherStateManager->registerAppliedPatches($repository, $appliedPatches); |
||
| 336 | |||
| 337 | $this->logger->reset($processIndentation); |
||
| 338 | } catch (\Vaimo\ComposerPatches\Exceptions\PatchFailureException $exception) { |
||
| 339 | $this->logger->push(); |
||
| 340 | |||
| 341 | $this->logger->write('error', $exception->getMessage()); |
||
| 342 | |||
| 343 | $previousError = $exception->getPrevious(); |
||
| 344 | |||
| 345 | if ($previousError) { |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 346 | $this->outputGenerator->generateForException($previousError); |
||
| 347 | } |
||
| 348 | |||
| 349 | $failedPath = $exception->getFailedPatchPath(); |
||
| 350 | |||
| 351 | $paths = array_keys($patchesQueue); |
||
| 352 | |||
| 353 | $failureIndex = array_search($failedPath, $paths, true); |
||
| 354 | |||
| 355 | $appliedPatches = array(); |
||
| 356 | |||
| 357 | if (is_int($failureIndex)) { |
||
| 358 | $appliedPaths = array_slice($paths, 0, $failureIndex); |
||
| 359 | |||
| 360 | $appliedPatches = array_intersect_key( |
||
| 361 | $patchesQueue, |
||
| 362 | array_flip($appliedPaths) |
||
| 363 | ); |
||
| 364 | } |
||
| 365 | |||
| 366 | $this->patcherStateManager->registerAppliedPatches($repository, $appliedPatches); |
||
| 367 | |||
| 368 | throw $exception; |
||
| 369 | } |
||
| 370 | } |
||
| 371 | |||
| 372 | private function shouldAllowOutput(array $patches, array $removals) |
||
| 373 | { |
||
| 374 | return $this->outputStrategy->shouldAllowForPatches($patches) |
||
| 375 | || $this->outputStrategy->shouldAllowForPatches($removals); |
||
| 376 | } |
||
| 377 | } |
||
| 378 |