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
![]() |
|||
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 |