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 Composer\Repository\WritableRepositoryInterface as PackageRepository; |
||||
13 | |||||
14 | use Vaimo\ComposerPatches\Composer\ConfigKeys; |
||||
15 | use Vaimo\ComposerPatches\Config; |
||||
16 | use Vaimo\ComposerPatches\Patch\Definition as Patch; |
||||
17 | use Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool; |
||||
18 | use Vaimo\ComposerPatches\Composer\Context as ComposerContext; |
||||
19 | use Vaimo\ComposerPatches\Utils\PathUtils; |
||||
20 | |||||
21 | /** |
||||
22 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||||
23 | */ |
||||
24 | class ValidateCommand extends \Composer\Command\BaseCommand |
||||
25 | { |
||||
26 | protected function configure() |
||||
27 | { |
||||
28 | parent::configure(); |
||||
29 | |||||
30 | $this->setName('patch:validate'); |
||||
31 | |||||
32 | $this->setDescription('Validate that all patches have proper target configuration'); |
||||
33 | |||||
34 | $this->addOption( |
||||
35 | '--from-source', |
||||
36 | null, |
||||
37 | InputOption::VALUE_NONE, |
||||
38 | 'Use latest information from package configurations in vendor folder' |
||||
39 | ); |
||||
40 | |||||
41 | $this->addOption( |
||||
42 | '--local', |
||||
43 | null, |
||||
44 | InputOption::VALUE_NONE, |
||||
45 | 'Only validate patches that are owned by the ROOT package' |
||||
46 | ); |
||||
47 | } |
||||
48 | |||||
49 | protected function execute(InputInterface $input, OutputInterface $output) |
||||
50 | { |
||||
51 | $output->writeln('<info>Scanning packages for orphan patches</info>'); |
||||
52 | |||||
53 | $composer = $this->getComposer(); |
||||
0 ignored issues
–
show
|
|||||
54 | |||||
55 | $localOnly = $input->getOption('local'); |
||||
56 | |||||
57 | $patchListAnalyser = new \Vaimo\ComposerPatches\Patch\DefinitionList\Analyser(); |
||||
58 | |||||
59 | $pluginConfig = array( |
||||
60 | Config::PATCHER_SOURCES => $this->createSourcesEnablerConfig($localOnly) |
||||
61 | ); |
||||
62 | |||||
63 | $contextFactory = new \Vaimo\ComposerPatches\Factories\ComposerContextFactory($composer); |
||||
64 | $composerContext = $contextFactory->create(); |
||||
65 | |||||
66 | $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composerContext, array( |
||||
67 | Config::PATCHER_FROM_SOURCE => (bool)$input->getOption('from-source') |
||||
68 | )); |
||||
69 | |||||
70 | $repository = $composer->getRepositoryManager()->getLocalRepository(); |
||||
71 | |||||
72 | $pluginConfig = $configFactory->create(array($pluginConfig)); |
||||
73 | |||||
74 | $patchesLoader = $this->createPatchesLoader($composerContext, $pluginConfig); |
||||
75 | |||||
76 | $patches = $patchesLoader->loadFromPackagesRepository($repository); |
||||
77 | |||||
78 | $patchPaths = $patchListAnalyser->extractValue($patches, array(Patch::PATH, Patch::SOURCE)); |
||||
79 | |||||
80 | $patchDefines = array_combine( |
||||
81 | $patchPaths, |
||||
82 | $patchListAnalyser->extractDictionary($patches, array(Patch::OWNER, Patch::URL)) |
||||
83 | ); |
||||
84 | |||||
85 | $patchStatuses = array_filter( |
||||
86 | array_combine( |
||||
87 | $patchPaths, |
||||
88 | $patchListAnalyser->extractValue($patches, array(Patch::STATUS_LABEL)) |
||||
89 | ) ?: array() |
||||
90 | ); |
||||
91 | |||||
92 | $matches = $this->resolveValidationTargets($repository, $pluginConfig); |
||||
93 | $installPaths = $this->collectInstallPaths($matches); |
||||
94 | $fileMatches = $this->collectPatchFilesFromPackages($matches, $pluginConfig); |
||||
95 | $groups = $this->collectOrphans($fileMatches, $patchDefines, $installPaths, $patchStatuses); |
||||
96 | $this->outputOrphans($output, $groups); |
||||
97 | |||||
98 | $output->writeln( |
||||
99 | $groups ? '<error>Orphans found!</error>' : '<info>Validation completed successfully</info>' |
||||
100 | ); |
||||
101 | |||||
102 | return (int)(bool)$groups; |
||||
103 | } |
||||
104 | |||||
105 | private function createSourcesEnablerConfig($localOnly) |
||||
106 | { |
||||
107 | $configDefaults = new Config\Defaults(); |
||||
108 | |||||
109 | $defaultValues = $configDefaults->getPatcherConfig(); |
||||
110 | |||||
111 | if (isset($defaultValues[Config::PATCHER_SOURCES]) && is_array($defaultValues[Config::PATCHER_SOURCES])) { |
||||
112 | $sourceKeys = array_keys((array)$defaultValues[Config::PATCHER_SOURCES]); |
||||
113 | |||||
114 | return $localOnly |
||||
115 | ? array_replace(array_fill_keys($sourceKeys, false), array('project' => true)) |
||||
116 | : array_fill_keys($sourceKeys, true); |
||||
117 | } |
||||
118 | |||||
119 | return array(); |
||||
120 | } |
||||
121 | |||||
122 | private function collectInstallPaths(array $matches) |
||||
123 | { |
||||
124 | $composer = $this->getComposer(); |
||||
0 ignored issues
–
show
The function
Composer\Command\BaseCommand::getComposer() has been deprecated: since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||
125 | $projectRoot = getcwd(); |
||||
126 | |||||
127 | $installationManager = $composer->getInstallationManager(); |
||||
128 | |||||
129 | $installPaths = array(); |
||||
130 | foreach ($matches as $packageName => $package) { |
||||
131 | $installPaths[$packageName] = $package instanceof \Composer\Package\RootPackageInterface |
||||
132 | ? $projectRoot |
||||
133 | : $installationManager->getInstallPath($package); |
||||
134 | } |
||||
135 | |||||
136 | return $installPaths; |
||||
137 | } |
||||
138 | |||||
139 | private function collectPatchFilesFromPackages(array $matches, Config $pluginConfig) |
||||
140 | { |
||||
141 | $composer = $this->getComposer(); |
||||
0 ignored issues
–
show
The function
Composer\Command\BaseCommand::getComposer() has been deprecated: since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||
142 | $composerConfig = $composer->getConfig(); |
||||
143 | |||||
144 | $configReaderFactory = new \Vaimo\ComposerPatches\Factories\PatcherConfigReaderFactory($composer); |
||||
145 | $dataUtils = new \Vaimo\ComposerPatches\Utils\DataUtils(); |
||||
146 | $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils(); |
||||
147 | $fileSystemUtils = new \Vaimo\ComposerPatches\Utils\FileSystemUtils(); |
||||
148 | |||||
149 | $projectRoot = getcwd(); |
||||
150 | |||||
151 | $vendorRoot = $composerConfig->get(ConfigKeys::VENDOR_DIR); |
||||
152 | $vendorPath = ltrim( |
||||
153 | substr($vendorRoot, strlen($projectRoot)), |
||||
154 | DIRECTORY_SEPARATOR |
||||
155 | ); |
||||
156 | |||||
157 | $defaultIgnores = array($vendorPath, '.hg', '.git', '.idea'); |
||||
158 | |||||
159 | $patcherConfigReader = $configReaderFactory->create($pluginConfig); |
||||
160 | |||||
161 | $installPaths = $this->collectInstallPaths($matches); |
||||
162 | |||||
163 | $fileMatchGroups = array(); |
||||
164 | |||||
165 | foreach ($matches as $packageName => $package) { |
||||
166 | $patcherConfig = $patcherConfigReader->readFromPackage($package); |
||||
167 | |||||
168 | $ignores = $dataUtils->getValueByPath( |
||||
169 | $patcherConfig, |
||||
170 | array(Config::PATCHER_CONFIG_ROOT, Config::PATCHES_IGNORE), |
||||
171 | array() |
||||
172 | ); |
||||
173 | |||||
174 | $installPath = $installPaths[$packageName]; |
||||
175 | |||||
176 | $skippedPaths = $dataUtils->prefixArrayValues( |
||||
177 | array_merge($defaultIgnores, $ignores), |
||||
178 | $installPath . DIRECTORY_SEPARATOR |
||||
179 | ); |
||||
180 | |||||
181 | $filter = $filterUtils->composeRegex( |
||||
182 | $filterUtils->invertRules($skippedPaths), |
||||
183 | '/' |
||||
184 | ); |
||||
185 | |||||
186 | $filter = sprintf('%s/i', rtrim($filter, '/') . Config::PATCH_FILE_REGEX_MATCHER); |
||||
187 | $searchResult = $fileSystemUtils->collectFilePathsRecursively($installPath, $filter); |
||||
188 | |||||
189 | $fileMatchGroups[] = array_fill_keys($searchResult, array( |
||||
190 | Patch::OWNER => $packageName, |
||||
191 | Patch::URL => '' |
||||
192 | )); |
||||
193 | } |
||||
194 | |||||
195 | return array_reduce($fileMatchGroups, 'array_replace', array()); |
||||
196 | } |
||||
197 | |||||
198 | private function resolveValidationTargets(PackageRepository $repository, Config $pluginConfig) |
||||
199 | { |
||||
200 | $composer = $this->getComposer(); |
||||
0 ignored issues
–
show
The function
Composer\Command\BaseCommand::getComposer() has been deprecated: since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||
201 | |||||
202 | $packageResolver = new \Vaimo\ComposerPatches\Composer\Plugin\PackageResolver( |
||||
203 | array($composer->getPackage()) |
||||
204 | ); |
||||
205 | |||||
206 | $srcResolverFactory = new \Vaimo\ComposerPatches\Factories\SourcesResolverFactory($composer); |
||||
207 | $packageListUtils = new \Vaimo\ComposerPatches\Utils\PackageListUtils(); |
||||
208 | |||||
209 | $srcResolver = $srcResolverFactory->create($pluginConfig); |
||||
210 | |||||
211 | $sources = $srcResolver->resolvePackages($repository); |
||||
212 | |||||
213 | $repositoryUtils = new \Vaimo\ComposerPatches\Utils\RepositoryUtils(); |
||||
214 | |||||
215 | $pluginPackage = $packageResolver->resolveForNamespace( |
||||
216 | $repository->getCanonicalPackages(), |
||||
217 | __NAMESPACE__ |
||||
218 | ); |
||||
219 | |||||
220 | $pluginName = $pluginPackage->getName(); |
||||
221 | |||||
222 | $pluginUsers = array_merge( |
||||
223 | $repositoryUtils->filterByDependency($repository, $pluginName), |
||||
224 | array($composer->getPackage()) |
||||
225 | ); |
||||
226 | |||||
227 | return array_intersect_key( |
||||
228 | $packageListUtils->listToNameDictionary($sources), |
||||
229 | $packageListUtils->listToNameDictionary($pluginUsers) |
||||
230 | ); |
||||
231 | } |
||||
232 | |||||
233 | private function createPatchesLoader(ComposerContext $composerContext, Config $pluginConfig) |
||||
234 | { |
||||
235 | $composer = $this->getComposer(); |
||||
0 ignored issues
–
show
The function
Composer\Command\BaseCommand::getComposer() has been deprecated: since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||
236 | |||||
237 | $loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer); |
||||
238 | |||||
239 | $componentOverrides = array( |
||||
240 | 'constraints' => false, |
||||
241 | 'platform' => false, |
||||
242 | 'targets-resolver' => false, |
||||
243 | 'local-exclude' => false, |
||||
244 | 'root-patch' => false, |
||||
245 | 'global-exclude' => false |
||||
246 | ); |
||||
247 | |||||
248 | $loaderComponentsPool = $this->createLoaderPool($composerContext, $componentOverrides); |
||||
249 | |||||
250 | return $loaderFactory->create($loaderComponentsPool, $pluginConfig, true); |
||||
251 | } |
||||
252 | |||||
253 | private function createLoaderPool(ComposerContext $composerContext, array $componentUpdates = array()) |
||||
254 | { |
||||
255 | $appIO = $this->getIO(); |
||||
256 | |||||
257 | $componentPool = new ComponentPool($composerContext, $appIO, true); |
||||
258 | |||||
259 | foreach ($componentUpdates as $componentName => $replacement) { |
||||
260 | $componentPool->registerComponent($componentName, $replacement); |
||||
261 | } |
||||
262 | |||||
263 | return $componentPool; |
||||
264 | } |
||||
265 | |||||
266 | private function collectOrphans(array $files, array $patches, array $paths, array $statuses) |
||||
267 | { |
||||
268 | $orphanFiles = array_diff_key($files, $patches); |
||||
269 | $orphanConfig = array_diff_key($patches, $files); |
||||
270 | |||||
271 | /** |
||||
272 | * Make sure that downloaded patches are not perceived as missing files |
||||
273 | */ |
||||
274 | $orphanConfig = array_diff_key( |
||||
275 | $orphanConfig, |
||||
276 | array_flip( |
||||
277 | array_filter(array_keys($orphanConfig), 'file_exists') |
||||
278 | ) |
||||
279 | ); |
||||
280 | |||||
281 | $groups = array_fill_keys(array_keys($paths), array()); |
||||
282 | |||||
283 | $results = array( |
||||
284 | $this->findWithMissingConfig($orphanFiles, $paths), |
||||
285 | $this->findWithMissingFile($orphanConfig, $paths, $statuses) |
||||
286 | ); |
||||
287 | |||||
288 | foreach ($results as $result) { |
||||
289 | foreach ($result as $ownerName => $items) { |
||||
290 | $groups[$ownerName] = array_merge($groups[$ownerName], $items); |
||||
291 | } |
||||
292 | } |
||||
293 | |||||
294 | return array_filter($groups); |
||||
295 | } |
||||
296 | |||||
297 | private function findWithMissingConfig($orphanFiles, $paths) |
||||
298 | { |
||||
299 | $pathFlags = array_fill_keys(array_keys($paths), true); |
||||
300 | $groups = array(); |
||||
301 | |||||
302 | foreach ($orphanFiles as $path => $config) { |
||||
303 | $ownerName = $config[Patch::OWNER]; |
||||
304 | $installPath = $paths[$ownerName]; |
||||
305 | |||||
306 | if (!isset($pathFlags[$ownerName])) { |
||||
307 | continue; |
||||
308 | } |
||||
309 | |||||
310 | $groups[$ownerName][] = array( |
||||
311 | 'issue' => 'NO CONFIG', |
||||
312 | 'path' => $config[Patch::URL] ?: PathUtils::reducePathLeft($path, $installPath) |
||||
313 | ); |
||||
314 | } |
||||
315 | |||||
316 | return $groups; |
||||
317 | } |
||||
318 | |||||
319 | private function findWithMissingFile($orphanConfig, $paths, $statuses) |
||||
320 | { |
||||
321 | $pathFlags = array_fill_keys(array_keys($paths), true); |
||||
322 | $groups = array(); |
||||
323 | |||||
324 | foreach ($orphanConfig as $path => $config) { |
||||
325 | $ownerName = $config[Patch::OWNER]; |
||||
326 | $installPath = $paths[$ownerName]; |
||||
327 | |||||
328 | if (!isset($pathFlags[$ownerName])) { |
||||
329 | continue; |
||||
330 | } |
||||
331 | |||||
332 | $groups[$ownerName][] = array( |
||||
333 | 'issue' => isset($statuses[$path]) && $statuses[$path] |
||||
334 | ? $statuses[$path] |
||||
335 | : 'NO FILE', |
||||
336 | 'path' => $config[Patch::URL] ?: PathUtils::reducePathLeft($path, $installPath) |
||||
337 | ); |
||||
338 | } |
||||
339 | |||||
340 | return $groups; |
||||
341 | } |
||||
342 | |||||
343 | private function outputOrphans(OutputInterface $output, array $groups) |
||||
344 | { |
||||
345 | $lines = array(); |
||||
346 | |||||
347 | foreach ($groups as $packageName => $items) { |
||||
348 | $lines[] = sprintf(' - <info>%s</info>', $packageName); |
||||
349 | |||||
350 | foreach ($items as $item) { |
||||
351 | $lines[] = sprintf( |
||||
352 | ' ~ %s [<fg=red>%s</>]', |
||||
353 | $item['path'], |
||||
354 | $item['issue'] |
||||
355 | ); |
||||
356 | } |
||||
357 | } |
||||
358 | |||||
359 | foreach ($lines as $line) { |
||||
360 | $output->writeln($line); |
||||
361 | } |
||||
362 | } |
||||
363 | } |
||||
364 |
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.