1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Copyright © Vaimo Group. All rights reserved. |
4
|
|
|
* See LICENSE_VAIMO.txt for license details. |
5
|
|
|
*/ |
6
|
|
|
namespace Vaimo\ComposerPatches\Composer\Commands; |
7
|
|
|
|
8
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
9
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
10
|
|
|
use Symfony\Component\Console\Input\InputOption; |
11
|
|
|
|
12
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
13
|
|
|
use Vaimo\ComposerPatches\Patch\Definition as Patch; |
14
|
|
|
|
15
|
|
|
use Composer\Composer; |
16
|
|
|
|
17
|
|
|
use Vaimo\ComposerPatches\Interfaces\ListResolverInterface as ListResolver; |
18
|
|
|
use Vaimo\ComposerPatches\Repository\PatchesApplier\ListResolvers; |
19
|
|
|
use Vaimo\ComposerPatches\Config; |
20
|
|
|
use Vaimo\ComposerPatches\Patch\DefinitionList\LoaderComponents; |
21
|
|
|
use Vaimo\ComposerPatches\Composer\Context as ComposerContext; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
25
|
|
|
*/ |
26
|
|
|
class ListCommand extends \Composer\Command\BaseCommand |
27
|
|
|
{ |
28
|
|
|
protected function configure() |
29
|
|
|
{ |
30
|
|
|
parent::configure(); |
31
|
|
|
|
32
|
|
|
$this->setName('patch:list'); |
33
|
|
|
|
34
|
|
|
$this->setDescription('List all registered and eligible (based on project config) patches'); |
35
|
|
|
|
36
|
|
|
$this->addArgument( |
37
|
|
|
'targets', |
38
|
|
|
InputArgument::IS_ARRAY, |
39
|
|
|
'Packages for the patcher to target', |
40
|
|
|
array() |
41
|
|
|
); |
42
|
|
|
|
43
|
|
|
$this->addOption( |
44
|
|
|
'--no-dev', |
45
|
|
|
null, |
46
|
|
|
InputOption::VALUE_NONE, |
47
|
|
|
'Disables installation of require-dev packages' |
48
|
|
|
); |
49
|
|
|
|
50
|
|
|
$this->addOption( |
51
|
|
|
'--filter', |
52
|
|
|
null, |
53
|
|
|
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, |
54
|
|
|
'Mark certain patches with MATCH in the output list' |
55
|
|
|
); |
56
|
|
|
|
57
|
|
|
$this->addOption( |
58
|
|
|
'--excluded', |
59
|
|
|
null, |
60
|
|
|
InputOption::VALUE_NONE, |
61
|
|
|
'Include patches that have been ruled out based on some constraint mismatch' |
62
|
|
|
); |
63
|
|
|
|
64
|
|
|
$this->addOption( |
65
|
|
|
'--with-excludes', |
66
|
|
|
null, |
67
|
|
|
InputOption::VALUE_NONE, |
68
|
|
|
'Alias for \'excluded\' argument' |
69
|
|
|
); |
70
|
|
|
|
71
|
|
|
$this->addOption( |
72
|
|
|
'--with-affected', |
73
|
|
|
null, |
74
|
|
|
InputOption::VALUE_NONE, |
75
|
|
|
'Mark patches that would get re-applied when changed/new patches are added (due to package reinstall)' |
76
|
|
|
); |
77
|
|
|
|
78
|
|
|
$this->addOption( |
79
|
|
|
'--brief', |
80
|
|
|
null, |
81
|
|
|
InputOption::VALUE_NONE, |
82
|
|
|
'Show more compact output of the list (remove description, owner , etc)' |
83
|
|
|
); |
84
|
|
|
|
85
|
|
|
$this->addOption( |
86
|
|
|
'--status', |
87
|
|
|
null, |
88
|
|
|
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, |
89
|
|
|
'Match specific statuses (changed, new, applied, removed)', |
90
|
|
|
array() |
91
|
|
|
); |
92
|
|
|
|
93
|
|
|
$this->addOption( |
94
|
|
|
'--from-source', |
95
|
|
|
null, |
96
|
|
|
InputOption::VALUE_NONE, |
97
|
|
|
'Use latest information from package configurations in vendor folder' |
98
|
|
|
); |
99
|
|
|
|
100
|
|
|
$this->addOption( |
101
|
|
|
'--json', |
102
|
|
|
null, |
103
|
|
|
InputOption::VALUE_NONE, |
104
|
|
|
'Output the list of patches in JSON format' |
105
|
|
|
); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @inheritDoc |
110
|
|
|
* |
111
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) |
112
|
|
|
*/ |
113
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
114
|
|
|
{ |
115
|
|
|
$composer = $this->getComposer(); |
|
|
|
|
116
|
|
|
|
117
|
|
|
$isDevMode = !$input->getOption('no-dev'); |
118
|
|
|
$withExcluded = $input->getOption('excluded') || $input->getOption('with-excludes'); |
119
|
|
|
$withAffected = $input->getOption('with-affected'); |
120
|
|
|
$beBrief = $input->getOption('brief'); |
121
|
|
|
|
122
|
|
|
$filters = array( |
123
|
|
|
Patch::SOURCE => $input->getOption('filter'), |
124
|
|
|
Patch::TARGETS => $input->getArgument('targets') |
125
|
|
|
); |
126
|
|
|
|
127
|
|
|
$statusFilters = array_map( |
128
|
|
|
'strtolower', |
129
|
|
|
array_filter((array)$input->getOption('status')) |
130
|
|
|
); |
131
|
|
|
|
132
|
|
|
$contextFactory = new \Vaimo\ComposerPatches\Factories\ComposerContextFactory($composer); |
133
|
|
|
$composerContext = $contextFactory->create(); |
134
|
|
|
|
135
|
|
|
$pluginConfig = $this->createConfigWithEnabledSources($composerContext); |
136
|
|
|
|
137
|
|
|
$filteredPool = $this->createLoaderPool($composerContext); |
138
|
|
|
|
139
|
|
|
$listResolver = new ListResolvers\FilteredListResolver($filters); |
140
|
|
|
$loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer); |
141
|
|
|
|
142
|
|
|
$repository = $composer->getRepositoryManager()->getLocalRepository(); |
143
|
|
|
|
144
|
|
|
$repoStateGenerator = $this->createStateGenerator($composer); |
145
|
|
|
$queueGenerator = $this->createQueueGenerator($listResolver); |
146
|
|
|
$filteredLoader = $loaderFactory->create($filteredPool, $pluginConfig, $isDevMode); |
147
|
|
|
|
148
|
|
|
$filteredPatches = $filteredLoader->loadFromPackagesRepository($repository); |
149
|
|
|
$repositoryState = $repoStateGenerator->generate($repository); |
150
|
|
|
|
151
|
|
|
$applyQueue = $queueGenerator->generateApplyQueue($filteredPatches, $repositoryState); |
152
|
|
|
$removeQueue = $queueGenerator->generateRemovalQueue($applyQueue, $repositoryState); |
153
|
|
|
$applyQueue = array_map('array_filter', $applyQueue); |
154
|
|
|
|
155
|
|
|
$patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils(); |
156
|
|
|
$patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater(); |
157
|
|
|
|
158
|
|
|
$filteredPatches = $this->composerFilteredPatchesList( |
159
|
|
|
$filteredPatches, |
160
|
|
|
$applyQueue, |
161
|
|
|
$removeQueue, |
162
|
|
|
$withAffected, |
163
|
|
|
$filters, |
164
|
|
|
$statusFilters |
165
|
|
|
); |
166
|
|
|
|
167
|
|
|
$patches = array_filter($filteredPatches); |
168
|
|
|
|
169
|
|
|
$filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils(); |
170
|
|
|
|
171
|
|
|
$shouldAddExcludes = $withExcluded |
172
|
|
|
&& ( |
173
|
|
|
empty($statusFilters) |
174
|
|
|
|| preg_match($filterUtils->composeRegex($statusFilters, '/'), 'excluded') |
175
|
|
|
); |
176
|
|
|
|
177
|
|
|
if ($shouldAddExcludes) { |
178
|
|
|
$unfilteredPool = $this->createUnfilteredPatchLoaderPool($composerContext); |
179
|
|
|
|
180
|
|
|
$unfilteredLoader = $loaderFactory->create($unfilteredPool, $pluginConfig, $isDevMode); |
181
|
|
|
|
182
|
|
|
$allPatches = $unfilteredLoader->loadFromPackagesRepository($repository); |
183
|
|
|
|
184
|
|
|
$patchesQueue = $listResolver->resolvePatchesQueue($allPatches); |
185
|
|
|
|
186
|
|
|
$excludedPatches = $patchListUpdater->updateStatuses( |
187
|
|
|
array_filter($patchListUtils->diffListsByPath($patchesQueue, $filteredPatches)), |
188
|
|
|
'excluded' |
189
|
|
|
); |
190
|
|
|
|
191
|
|
|
$patches = array_replace_recursive( |
192
|
|
|
$patches, |
193
|
|
|
$patchListUpdater->updateStatuses($excludedPatches, 'excluded') |
194
|
|
|
); |
195
|
|
|
|
196
|
|
|
array_walk($patches, function (array &$group) { |
197
|
|
|
ksort($group); |
198
|
|
|
}, $patches); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
if ($beBrief) { |
202
|
|
|
$patches = $patchListUpdater->embedInfoToItems($patches, array( |
203
|
|
|
Patch::LABEL => false, |
204
|
|
|
Patch::OWNER => false |
205
|
|
|
)); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
if ($input->getOption('json')) { |
209
|
|
|
$output->writeln(json_encode($patches)); |
210
|
|
|
|
211
|
|
|
return 0; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$this->generateOutput($output, $patches); |
215
|
|
|
|
216
|
|
|
return 0; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
private function createUnfilteredPatchLoaderPool(\Vaimo\ComposerPatches\Composer\Context $composerContext) |
220
|
|
|
{ |
221
|
|
|
$composer = $composerContext->getLocalComposer(); |
222
|
|
|
|
223
|
|
|
$packageInfoResolver = new \Vaimo\ComposerPatches\Package\InfoResolver( |
224
|
|
|
$composer->getInstallationManager(), |
225
|
|
|
$composer->getConfig()->get(\Vaimo\ComposerPatches\Composer\ConfigKeys::VENDOR_DIR) |
226
|
|
|
); |
227
|
|
|
|
228
|
|
|
$componentOverrides = array( |
229
|
|
|
'constraints' => false, |
230
|
|
|
'platform' => false, |
231
|
|
|
'local-exclude' => false, |
232
|
|
|
'root-patch' => false, |
233
|
|
|
'global-exclude' => false, |
234
|
|
|
'targets-resolver' => new LoaderComponents\TargetsResolverComponent($packageInfoResolver, true) |
235
|
|
|
); |
236
|
|
|
|
237
|
|
|
return $this->createLoaderPool($composerContext, $componentOverrides); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
private function composerFilteredPatchesList($patches, $additions, $removals, $withAffected, $filters, $statuses) |
241
|
|
|
{ |
242
|
|
|
$hasFilers = (bool)array_filter($filters); |
243
|
|
|
|
244
|
|
|
$listResolver = new ListResolvers\FilteredListResolver($filters); |
245
|
|
|
|
246
|
|
|
$patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils(); |
247
|
|
|
$patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater(); |
248
|
|
|
|
249
|
|
|
$filteredPatches = $patchListUtils->mergeLists($patches, $removals); |
250
|
|
|
|
251
|
|
|
if ($withAffected) { |
252
|
|
|
$additions = $patchListUpdater->embedInfoToItems( |
253
|
|
|
$additions, |
254
|
|
|
array(Patch::STATUS => 'affected'), |
255
|
|
|
true |
256
|
|
|
); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$filteredPatches = $patchListUtils->mergeLists( |
260
|
|
|
$filteredPatches, |
261
|
|
|
$patchListUtils->intersectListsByName($additions, $filteredPatches) |
262
|
|
|
); |
263
|
|
|
|
264
|
|
|
$filteredPatches = $patchListUpdater->embedInfoToItems( |
265
|
|
|
$filteredPatches, |
266
|
|
|
array(Patch::STATUS => 'applied'), |
267
|
|
|
true |
268
|
|
|
); |
269
|
|
|
|
270
|
|
|
if ($hasFilers) { |
271
|
|
|
$filteredPatches = $listResolver->resolvePatchesQueue($filteredPatches); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
if (!empty($statuses)) { |
275
|
|
|
$filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils(); |
276
|
|
|
|
277
|
|
|
$filteredPatches = $patchListUtils->applyDefinitionKeyValueFilter( |
278
|
|
|
$filteredPatches, |
279
|
|
|
$filterUtils->composeRegex($statuses, '/'), |
280
|
|
|
Patch::STATUS |
281
|
|
|
); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return $filteredPatches; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
private function createConfigWithEnabledSources(\Vaimo\ComposerPatches\Composer\Context $composerContext) |
288
|
|
|
{ |
289
|
|
|
$configDefaults = new \Vaimo\ComposerPatches\Config\Defaults(); |
290
|
|
|
|
291
|
|
|
$defaultValues = $configDefaults->getPatcherConfig(); |
292
|
|
|
|
293
|
|
|
$sourceKeys = array(); |
294
|
|
|
|
295
|
|
|
if (isset($defaultValues[Config::PATCHER_SOURCES]) && is_array($defaultValues[Config::PATCHER_SOURCES])) { |
296
|
|
|
$sourceKeys = array_keys((array)$defaultValues[Config::PATCHER_SOURCES]); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
$pluginConfig = array( |
300
|
|
|
Config::PATCHER_SOURCES => array_fill_keys($sourceKeys, true) |
301
|
|
|
); |
302
|
|
|
|
303
|
|
|
$configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composerContext); |
304
|
|
|
|
305
|
|
|
return $configFactory->create(array($pluginConfig)); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
private function createStateGenerator(Composer $composer) |
309
|
|
|
{ |
310
|
|
|
$packageCollector = new \Vaimo\ComposerPatches\Package\Collector( |
311
|
|
|
array($composer->getPackage()) |
312
|
|
|
); |
313
|
|
|
|
314
|
|
|
return new \Vaimo\ComposerPatches\Repository\StateGenerator( |
315
|
|
|
$packageCollector |
316
|
|
|
); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
private function createQueueGenerator(ListResolver $listResolver) |
320
|
|
|
{ |
321
|
|
|
$changesListResolver = new ListResolvers\ChangesListResolver($listResolver); |
322
|
|
|
$stateAnalyser = new \Vaimo\ComposerPatches\Repository\State\Analyser(); |
323
|
|
|
|
324
|
|
|
return new \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator( |
325
|
|
|
$changesListResolver, |
326
|
|
|
$stateAnalyser |
327
|
|
|
); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
private function createLoaderPool(ComposerContext $composerContext, array $componentUpdates = array()) |
331
|
|
|
{ |
332
|
|
|
$componentPool = new \Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool( |
333
|
|
|
$composerContext, |
334
|
|
|
$this->getIO(), |
335
|
|
|
true |
336
|
|
|
); |
337
|
|
|
|
338
|
|
|
foreach ($componentUpdates as $componentName => $replacement) { |
339
|
|
|
$componentPool->registerComponent($componentName, $replacement); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
return $componentPool; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
private function generateOutput(OutputInterface $output, array $list) |
346
|
|
|
{ |
347
|
|
|
$statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig(); |
348
|
|
|
|
349
|
|
|
$statusDecorators = $statusConfig->getLabels(); |
350
|
|
|
|
351
|
|
|
foreach ($list as $packageName => $patches) { |
352
|
|
|
$output->writeln(sprintf('<info>%s</info>', $packageName)); |
353
|
|
|
|
354
|
|
|
foreach ($patches as $path => $info) { |
355
|
|
|
$owner = $info[Patch::OWNER]; |
356
|
|
|
$patchInfoLabel = $this->createStatusLabel($path, $info, $statusDecorators); |
357
|
|
|
$output->writeln($owner ? sprintf(' ~ %s', $patchInfoLabel) : $patchInfoLabel); |
358
|
|
|
|
359
|
|
|
$descriptionLines = array_filter( |
360
|
|
|
explode(PHP_EOL, $info[Patch::LABEL]) |
361
|
|
|
); |
362
|
|
|
|
363
|
|
|
foreach ($descriptionLines as $line) { |
364
|
|
|
$output->writeln(sprintf(' <comment>%s</comment>', $line)); |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$output->writeln(''); |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
private function createStatusLabel($path, $info, array $statusDecorators) |
373
|
|
|
{ |
374
|
|
|
$status = isset($info[Patch::STATUS]) |
375
|
|
|
? $info[Patch::STATUS] |
376
|
|
|
: Patch::STATUS_UNKNOWN; |
377
|
|
|
|
378
|
|
|
$owner = $info[Patch::OWNER]; |
379
|
|
|
|
380
|
|
|
$stateDecorator = $statusDecorators[$status]; |
381
|
|
|
|
382
|
|
|
if ($status === Patch::STATUS_ERRORS) { |
383
|
|
|
$stateDecorator = sprintf( |
384
|
|
|
$stateDecorator, |
385
|
|
|
$info[Patch::STATE_LABEL] ? $info[Patch::STATE_LABEL] : 'ERROR' |
386
|
|
|
); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
$statusLabel = sprintf(' [%s]', $stateDecorator); |
390
|
|
|
|
391
|
|
|
if ($owner && $owner !== Patch::OWNER_UNKNOWN) { |
392
|
|
|
return sprintf('<info>%s</info>: %s%s', $owner, $path, $statusLabel); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
return sprintf('%s%s', $path, $statusLabel); |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.