Mercurial > hg > cmmr2012-drupal-site
comparison vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 /** | |
3 * This file is part of the Composer Merge plugin. | |
4 * | |
5 * Copyright (C) 2015 Bryan Davis, Wikimedia Foundation, and contributors | |
6 * | |
7 * This software may be modified and distributed under the terms of the MIT | |
8 * license. See the LICENSE file for details. | |
9 */ | |
10 | |
11 namespace Wikimedia\Composer\Merge; | |
12 | |
13 use Wikimedia\Composer\Logger; | |
14 | |
15 use Composer\Composer; | |
16 use Composer\Json\JsonFile; | |
17 use Composer\Package\BasePackage; | |
18 use Composer\Package\CompletePackage; | |
19 use Composer\Package\Link; | |
20 use Composer\Package\Loader\ArrayLoader; | |
21 use Composer\Package\RootAliasPackage; | |
22 use Composer\Package\RootPackage; | |
23 use Composer\Package\RootPackageInterface; | |
24 use Composer\Package\Version\VersionParser; | |
25 use UnexpectedValueException; | |
26 | |
27 /** | |
28 * Processing for a composer.json file that will be merged into | |
29 * a RootPackageInterface | |
30 * | |
31 * @author Bryan Davis <bd808@bd808.com> | |
32 */ | |
33 class ExtraPackage | |
34 { | |
35 | |
36 /** | |
37 * @var Composer $composer | |
38 */ | |
39 protected $composer; | |
40 | |
41 /** | |
42 * @var Logger $logger | |
43 */ | |
44 protected $logger; | |
45 | |
46 /** | |
47 * @var string $path | |
48 */ | |
49 protected $path; | |
50 | |
51 /** | |
52 * @var array $json | |
53 */ | |
54 protected $json; | |
55 | |
56 /** | |
57 * @var CompletePackage $package | |
58 */ | |
59 protected $package; | |
60 | |
61 /** | |
62 * @var VersionParser $versionParser | |
63 */ | |
64 protected $versionParser; | |
65 | |
66 /** | |
67 * @param string $path Path to composer.json file | |
68 * @param Composer $composer | |
69 * @param Logger $logger | |
70 */ | |
71 public function __construct($path, Composer $composer, Logger $logger) | |
72 { | |
73 $this->path = $path; | |
74 $this->composer = $composer; | |
75 $this->logger = $logger; | |
76 $this->json = $this->readPackageJson($path); | |
77 $this->package = $this->loadPackage($this->json); | |
78 $this->versionParser = new VersionParser(); | |
79 } | |
80 | |
81 /** | |
82 * Get list of additional packages to include if precessing recursively. | |
83 * | |
84 * @return array | |
85 */ | |
86 public function getIncludes() | |
87 { | |
88 return isset($this->json['extra']['merge-plugin']['include']) ? | |
89 $this->json['extra']['merge-plugin']['include'] : array(); | |
90 } | |
91 | |
92 /** | |
93 * Get list of additional packages to require if precessing recursively. | |
94 * | |
95 * @return array | |
96 */ | |
97 public function getRequires() | |
98 { | |
99 return isset($this->json['extra']['merge-plugin']['require']) ? | |
100 $this->json['extra']['merge-plugin']['require'] : array(); | |
101 } | |
102 | |
103 /** | |
104 * Read the contents of a composer.json style file into an array. | |
105 * | |
106 * The package contents are fixed up to be usable to create a Package | |
107 * object by providing dummy "name" and "version" values if they have not | |
108 * been provided in the file. This is consistent with the default root | |
109 * package loading behavior of Composer. | |
110 * | |
111 * @param string $path | |
112 * @return array | |
113 */ | |
114 protected function readPackageJson($path) | |
115 { | |
116 $file = new JsonFile($path); | |
117 $json = $file->read(); | |
118 if (!isset($json['name'])) { | |
119 $json['name'] = 'merge-plugin/' . | |
120 strtr($path, DIRECTORY_SEPARATOR, '-'); | |
121 } | |
122 if (!isset($json['version'])) { | |
123 $json['version'] = '1.0.0'; | |
124 } | |
125 return $json; | |
126 } | |
127 | |
128 /** | |
129 * @param array $json | |
130 * @return CompletePackage | |
131 */ | |
132 protected function loadPackage(array $json) | |
133 { | |
134 $loader = new ArrayLoader(); | |
135 $package = $loader->load($json); | |
136 // @codeCoverageIgnoreStart | |
137 if (!$package instanceof CompletePackage) { | |
138 throw new UnexpectedValueException( | |
139 'Expected instance of CompletePackage, got ' . | |
140 get_class($package) | |
141 ); | |
142 } | |
143 // @codeCoverageIgnoreEnd | |
144 return $package; | |
145 } | |
146 | |
147 /** | |
148 * Merge this package into a RootPackageInterface | |
149 * | |
150 * @param RootPackageInterface $root | |
151 * @param PluginState $state | |
152 */ | |
153 public function mergeInto(RootPackageInterface $root, PluginState $state) | |
154 { | |
155 $this->prependRepositories($root); | |
156 | |
157 $this->mergeRequires('require', $root, $state); | |
158 | |
159 $this->mergePackageLinks('conflict', $root); | |
160 $this->mergePackageLinks('replace', $root); | |
161 $this->mergePackageLinks('provide', $root); | |
162 | |
163 $this->mergeSuggests($root); | |
164 | |
165 $this->mergeAutoload('autoload', $root); | |
166 | |
167 $this->mergeExtra($root, $state); | |
168 | |
169 $this->mergeScripts($root, $state); | |
170 | |
171 if ($state->isDevMode()) { | |
172 $this->mergeDevInto($root, $state); | |
173 } else { | |
174 $this->mergeReferences($root); | |
175 } | |
176 } | |
177 | |
178 /** | |
179 * Merge just the dev portion into a RootPackageInterface | |
180 * | |
181 * @param RootPackageInterface $root | |
182 * @param PluginState $state | |
183 */ | |
184 public function mergeDevInto(RootPackageInterface $root, PluginState $state) | |
185 { | |
186 $this->mergeRequires('require-dev', $root, $state); | |
187 $this->mergeAutoload('devAutoload', $root); | |
188 $this->mergeReferences($root); | |
189 } | |
190 | |
191 /** | |
192 * Add a collection of repositories described by the given configuration | |
193 * to the given package and the global repository manager. | |
194 * | |
195 * @param RootPackageInterface $root | |
196 */ | |
197 protected function prependRepositories(RootPackageInterface $root) | |
198 { | |
199 if (!isset($this->json['repositories'])) { | |
200 return; | |
201 } | |
202 $repoManager = $this->composer->getRepositoryManager(); | |
203 $newRepos = array(); | |
204 | |
205 foreach ($this->json['repositories'] as $repoJson) { | |
206 if (!isset($repoJson['type'])) { | |
207 continue; | |
208 } | |
209 $this->logger->info("Prepending {$repoJson['type']} repository"); | |
210 $repo = $repoManager->createRepository( | |
211 $repoJson['type'], | |
212 $repoJson | |
213 ); | |
214 $repoManager->prependRepository($repo); | |
215 $newRepos[] = $repo; | |
216 } | |
217 | |
218 $unwrapped = self::unwrapIfNeeded($root, 'setRepositories'); | |
219 $unwrapped->setRepositories(array_merge( | |
220 $newRepos, | |
221 $root->getRepositories() | |
222 )); | |
223 } | |
224 | |
225 /** | |
226 * Merge require or require-dev into a RootPackageInterface | |
227 * | |
228 * @param string $type 'require' or 'require-dev' | |
229 * @param RootPackageInterface $root | |
230 * @param PluginState $state | |
231 */ | |
232 protected function mergeRequires( | |
233 $type, | |
234 RootPackageInterface $root, | |
235 PluginState $state | |
236 ) { | |
237 $linkType = BasePackage::$supportedLinkTypes[$type]; | |
238 $getter = 'get' . ucfirst($linkType['method']); | |
239 $setter = 'set' . ucfirst($linkType['method']); | |
240 | |
241 $requires = $this->package->{$getter}(); | |
242 if (empty($requires)) { | |
243 return; | |
244 } | |
245 | |
246 $this->mergeStabilityFlags($root, $requires); | |
247 | |
248 $requires = $this->replaceSelfVersionDependencies( | |
249 $type, | |
250 $requires, | |
251 $root | |
252 ); | |
253 | |
254 $root->{$setter}($this->mergeOrDefer( | |
255 $type, | |
256 $root->{$getter}(), | |
257 $requires, | |
258 $state | |
259 )); | |
260 } | |
261 | |
262 /** | |
263 * Merge two collections of package links and collect duplicates for | |
264 * subsequent processing. | |
265 * | |
266 * @param string $type 'require' or 'require-dev' | |
267 * @param array $origin Primary collection | |
268 * @param array $merge Additional collection | |
269 * @param PluginState $state | |
270 * @return array Merged collection | |
271 */ | |
272 protected function mergeOrDefer( | |
273 $type, | |
274 array $origin, | |
275 array $merge, | |
276 $state | |
277 ) { | |
278 if ($state->ignoreDuplicateLinks() && $state->replaceDuplicateLinks()) { | |
279 $this->logger->warning("Both replace and ignore-duplicates are true. These are mutually exclusive."); | |
280 $this->logger->warning("Duplicate packages will be ignored."); | |
281 } | |
282 | |
283 $dups = array(); | |
284 foreach ($merge as $name => $link) { | |
285 if (isset($origin[$name]) && $state->ignoreDuplicateLinks()) { | |
286 $this->logger->info("Ignoring duplicate <comment>{$name}</comment>"); | |
287 continue; | |
288 } elseif (!isset($origin[$name]) || $state->replaceDuplicateLinks()) { | |
289 $this->logger->info("Merging <comment>{$name}</comment>"); | |
290 $origin[$name] = $link; | |
291 } else { | |
292 // Defer to solver. | |
293 $this->logger->info( | |
294 "Deferring duplicate <comment>{$name}</comment>" | |
295 ); | |
296 $dups[] = $link; | |
297 } | |
298 } | |
299 $state->addDuplicateLinks($type, $dups); | |
300 return $origin; | |
301 } | |
302 | |
303 /** | |
304 * Merge autoload or autoload-dev into a RootPackageInterface | |
305 * | |
306 * @param string $type 'autoload' or 'devAutoload' | |
307 * @param RootPackageInterface $root | |
308 */ | |
309 protected function mergeAutoload($type, RootPackageInterface $root) | |
310 { | |
311 $getter = 'get' . ucfirst($type); | |
312 $setter = 'set' . ucfirst($type); | |
313 | |
314 $autoload = $this->package->{$getter}(); | |
315 if (empty($autoload)) { | |
316 return; | |
317 } | |
318 | |
319 $unwrapped = self::unwrapIfNeeded($root, $setter); | |
320 $unwrapped->{$setter}(array_merge_recursive( | |
321 $root->{$getter}(), | |
322 $this->fixRelativePaths($autoload) | |
323 )); | |
324 } | |
325 | |
326 /** | |
327 * Fix a collection of paths that are relative to this package to be | |
328 * relative to the base package. | |
329 * | |
330 * @param array $paths | |
331 * @return array | |
332 */ | |
333 protected function fixRelativePaths(array $paths) | |
334 { | |
335 $base = dirname($this->path); | |
336 $base = ($base === '.') ? '' : "{$base}/"; | |
337 | |
338 array_walk_recursive( | |
339 $paths, | |
340 function (&$path) use ($base) { | |
341 $path = "{$base}{$path}"; | |
342 } | |
343 ); | |
344 return $paths; | |
345 } | |
346 | |
347 /** | |
348 * Extract and merge stability flags from the given collection of | |
349 * requires and merge them into a RootPackageInterface | |
350 * | |
351 * @param RootPackageInterface $root | |
352 * @param array $requires | |
353 */ | |
354 protected function mergeStabilityFlags( | |
355 RootPackageInterface $root, | |
356 array $requires | |
357 ) { | |
358 $flags = $root->getStabilityFlags(); | |
359 $sf = new StabilityFlags($flags, $root->getMinimumStability()); | |
360 | |
361 $unwrapped = self::unwrapIfNeeded($root, 'setStabilityFlags'); | |
362 $unwrapped->setStabilityFlags(array_merge( | |
363 $flags, | |
364 $sf->extractAll($requires) | |
365 )); | |
366 } | |
367 | |
368 /** | |
369 * Merge package links of the given type into a RootPackageInterface | |
370 * | |
371 * @param string $type 'conflict', 'replace' or 'provide' | |
372 * @param RootPackageInterface $root | |
373 */ | |
374 protected function mergePackageLinks($type, RootPackageInterface $root) | |
375 { | |
376 $linkType = BasePackage::$supportedLinkTypes[$type]; | |
377 $getter = 'get' . ucfirst($linkType['method']); | |
378 $setter = 'set' . ucfirst($linkType['method']); | |
379 | |
380 $links = $this->package->{$getter}(); | |
381 if (!empty($links)) { | |
382 $unwrapped = self::unwrapIfNeeded($root, $setter); | |
383 // @codeCoverageIgnoreStart | |
384 if ($root !== $unwrapped) { | |
385 $this->logger->warning( | |
386 'This Composer version does not support ' . | |
387 "'{$type}' merging for aliased packages." | |
388 ); | |
389 } | |
390 // @codeCoverageIgnoreEnd | |
391 $unwrapped->{$setter}(array_merge( | |
392 $root->{$getter}(), | |
393 $this->replaceSelfVersionDependencies($type, $links, $root) | |
394 )); | |
395 } | |
396 } | |
397 | |
398 /** | |
399 * Merge suggested packages into a RootPackageInterface | |
400 * | |
401 * @param RootPackageInterface $root | |
402 */ | |
403 protected function mergeSuggests(RootPackageInterface $root) | |
404 { | |
405 $suggests = $this->package->getSuggests(); | |
406 if (!empty($suggests)) { | |
407 $unwrapped = self::unwrapIfNeeded($root, 'setSuggests'); | |
408 $unwrapped->setSuggests(array_merge( | |
409 $root->getSuggests(), | |
410 $suggests | |
411 )); | |
412 } | |
413 } | |
414 | |
415 /** | |
416 * Merge extra config into a RootPackageInterface | |
417 * | |
418 * @param RootPackageInterface $root | |
419 * @param PluginState $state | |
420 */ | |
421 public function mergeExtra(RootPackageInterface $root, PluginState $state) | |
422 { | |
423 $extra = $this->package->getExtra(); | |
424 unset($extra['merge-plugin']); | |
425 if (!$state->shouldMergeExtra() || empty($extra)) { | |
426 return; | |
427 } | |
428 | |
429 $rootExtra = $root->getExtra(); | |
430 $unwrapped = self::unwrapIfNeeded($root, 'setExtra'); | |
431 | |
432 if ($state->replaceDuplicateLinks()) { | |
433 $unwrapped->setExtra( | |
434 self::mergeExtraArray($state->shouldMergeExtraDeep(), $rootExtra, $extra) | |
435 ); | |
436 } else { | |
437 if (!$state->shouldMergeExtraDeep()) { | |
438 foreach (array_intersect( | |
439 array_keys($extra), | |
440 array_keys($rootExtra) | |
441 ) as $key) { | |
442 $this->logger->info( | |
443 "Ignoring duplicate <comment>{$key}</comment> in ". | |
444 "<comment>{$this->path}</comment> extra config." | |
445 ); | |
446 } | |
447 } | |
448 $unwrapped->setExtra( | |
449 self::mergeExtraArray($state->shouldMergeExtraDeep(), $extra, $rootExtra) | |
450 ); | |
451 } | |
452 } | |
453 | |
454 /** | |
455 * Merge scripts config into a RootPackageInterface | |
456 * | |
457 * @param RootPackageInterface $root | |
458 * @param PluginState $state | |
459 */ | |
460 public function mergeScripts(RootPackageInterface $root, PluginState $state) | |
461 { | |
462 $scripts = $this->package->getScripts(); | |
463 if (!$state->shouldMergeScripts() || empty($scripts)) { | |
464 return; | |
465 } | |
466 | |
467 $rootScripts = $root->getScripts(); | |
468 $unwrapped = self::unwrapIfNeeded($root, 'setScripts'); | |
469 | |
470 if ($state->replaceDuplicateLinks()) { | |
471 $unwrapped->setScripts( | |
472 array_merge($rootScripts, $scripts) | |
473 ); | |
474 } else { | |
475 $unwrapped->setScripts( | |
476 array_merge($scripts, $rootScripts) | |
477 ); | |
478 } | |
479 } | |
480 | |
481 /** | |
482 * Merges two arrays either via arrayMergeDeep or via array_merge. | |
483 * | |
484 * @param bool $mergeDeep | |
485 * @param array $array1 | |
486 * @param array $array2 | |
487 * @return array | |
488 */ | |
489 public static function mergeExtraArray($mergeDeep, $array1, $array2) | |
490 { | |
491 if ($mergeDeep) { | |
492 return NestedArray::mergeDeep($array1, $array2); | |
493 } | |
494 | |
495 return array_merge($array1, $array2); | |
496 } | |
497 | |
498 /** | |
499 * Update Links with a 'self.version' constraint with the root package's | |
500 * version. | |
501 * | |
502 * @param string $type Link type | |
503 * @param array $links | |
504 * @param RootPackageInterface $root | |
505 * @return array | |
506 */ | |
507 protected function replaceSelfVersionDependencies( | |
508 $type, | |
509 array $links, | |
510 RootPackageInterface $root | |
511 ) { | |
512 $linkType = BasePackage::$supportedLinkTypes[$type]; | |
513 $version = $root->getVersion(); | |
514 $prettyVersion = $root->getPrettyVersion(); | |
515 $vp = $this->versionParser; | |
516 | |
517 $method = 'get' . ucfirst($linkType['method']); | |
518 $packages = $root->$method(); | |
519 | |
520 return array_map( | |
521 function ($link) use ($linkType, $version, $prettyVersion, $vp, $packages) { | |
522 if ('self.version' === $link->getPrettyConstraint()) { | |
523 if (isset($packages[$link->getSource()])) { | |
524 /** @var Link $package */ | |
525 $package = $packages[$link->getSource()]; | |
526 return new Link( | |
527 $link->getSource(), | |
528 $link->getTarget(), | |
529 $vp->parseConstraints($package->getConstraint()->getPrettyString()), | |
530 $linkType['description'], | |
531 $package->getPrettyConstraint() | |
532 ); | |
533 } | |
534 | |
535 return new Link( | |
536 $link->getSource(), | |
537 $link->getTarget(), | |
538 $vp->parseConstraints($version), | |
539 $linkType['description'], | |
540 $prettyVersion | |
541 ); | |
542 } | |
543 return $link; | |
544 }, | |
545 $links | |
546 ); | |
547 } | |
548 | |
549 /** | |
550 * Get a full featured Package from a RootPackageInterface. | |
551 * | |
552 * In Composer versions before 599ad77 the RootPackageInterface only | |
553 * defines a sub-set of operations needed by composer-merge-plugin and | |
554 * RootAliasPackage only implemented those methods defined by the | |
555 * interface. Most of the unimplemented methods in RootAliasPackage can be | |
556 * worked around because the getter methods that are implemented proxy to | |
557 * the aliased package which we can modify by unwrapping. The exception | |
558 * being modifying the 'conflicts', 'provides' and 'replaces' collections. | |
559 * We have no way to actually modify those collections unfortunately in | |
560 * older versions of Composer. | |
561 * | |
562 * @param RootPackageInterface $root | |
563 * @param string $method Method needed | |
564 * @return RootPackageInterface|RootPackage | |
565 */ | |
566 public static function unwrapIfNeeded( | |
567 RootPackageInterface $root, | |
568 $method = 'setExtra' | |
569 ) { | |
570 // @codeCoverageIgnoreStart | |
571 if ($root instanceof RootAliasPackage && | |
572 !method_exists($root, $method) | |
573 ) { | |
574 // Unwrap and return the aliased RootPackage. | |
575 $root = $root->getAliasOf(); | |
576 } | |
577 // @codeCoverageIgnoreEnd | |
578 return $root; | |
579 } | |
580 | |
581 /** | |
582 * Update the root packages reference information. | |
583 * | |
584 * @param RootPackageInterface $root | |
585 */ | |
586 protected function mergeReferences(RootPackageInterface $root) | |
587 { | |
588 // Merge source reference information for merged packages. | |
589 // @see RootPackageLoader::load | |
590 $references = array(); | |
591 $unwrapped = $this->unwrapIfNeeded($root, 'setReferences'); | |
592 foreach (array('require', 'require-dev') as $linkType) { | |
593 $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; | |
594 $method = 'get'.ucfirst($linkInfo['method']); | |
595 $links = array(); | |
596 foreach ($unwrapped->$method() as $link) { | |
597 $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); | |
598 } | |
599 $references = $this->extractReferences($links, $references); | |
600 } | |
601 $unwrapped->setReferences($references); | |
602 } | |
603 | |
604 /** | |
605 * Extract vcs revision from version constraint (dev-master#abc123. | |
606 * | |
607 * @param array $requires | |
608 * @param array $references | |
609 * @return array | |
610 * @see RootPackageLoader::extractReferences() | |
611 */ | |
612 protected function extractReferences(array $requires, array $references) | |
613 { | |
614 foreach ($requires as $reqName => $reqVersion) { | |
615 $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); | |
616 $stabilityName = VersionParser::parseStability($reqVersion); | |
617 if ( | |
618 preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && | |
619 $stabilityName === 'dev' | |
620 ) { | |
621 $name = strtolower($reqName); | |
622 $references[$name] = $match[1]; | |
623 } | |
624 } | |
625 | |
626 return $references; | |
627 } | |
628 } | |
629 // vim:sw=4:ts=4:sts=4:et: |