PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php

https://gitlab.com/geeta7/drupal
PHP | 487 lines | 286 code | 46 blank | 155 comment | 22 complexity | 2b3f460088d29ccac4f4e470d1ab858f MD5 | raw file
  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. namespace Wikimedia\Composer\Merge;
  11. use Wikimedia\Composer\Logger;
  12. use Composer\Composer;
  13. use Composer\Json\JsonFile;
  14. use Composer\Package\BasePackage;
  15. use Composer\Package\CompletePackage;
  16. use Composer\Package\Link;
  17. use Composer\Package\Loader\ArrayLoader;
  18. use Composer\Package\RootAliasPackage;
  19. use Composer\Package\RootPackage;
  20. use Composer\Package\RootPackageInterface;
  21. use Composer\Package\Version\VersionParser;
  22. use UnexpectedValueException;
  23. /**
  24. * Processing for a composer.json file that will be merged into
  25. * a RootPackageInterface
  26. *
  27. * @author Bryan Davis <bd808@bd808.com>
  28. */
  29. class ExtraPackage
  30. {
  31. /**
  32. * @var Composer $composer
  33. */
  34. protected $composer;
  35. /**
  36. * @var Logger $logger
  37. */
  38. protected $logger;
  39. /**
  40. * @var string $path
  41. */
  42. protected $path;
  43. /**
  44. * @var array $json
  45. */
  46. protected $json;
  47. /**
  48. * @var CompletePackage $package
  49. */
  50. protected $package;
  51. /**
  52. * @param string $path Path to composer.json file
  53. * @param Composer $composer
  54. * @param Logger $logger
  55. */
  56. public function __construct($path, Composer $composer, Logger $logger)
  57. {
  58. $this->path = $path;
  59. $this->composer = $composer;
  60. $this->logger = $logger;
  61. $this->json = $this->readPackageJson($path);
  62. $this->package = $this->loadPackage($this->json);
  63. }
  64. /**
  65. * Get list of additional packages to include if precessing recursively.
  66. *
  67. * @return array
  68. */
  69. public function getIncludes()
  70. {
  71. return isset($this->json['extra']['merge-plugin']['include']) ?
  72. $this->json['extra']['merge-plugin']['include'] : array();
  73. }
  74. /**
  75. * Get list of additional packages to require if precessing recursively.
  76. *
  77. * @return array
  78. */
  79. public function getRequires()
  80. {
  81. return isset($this->json['extra']['merge-plugin']['require']) ?
  82. $this->json['extra']['merge-plugin']['require'] : array();
  83. }
  84. /**
  85. * Read the contents of a composer.json style file into an array.
  86. *
  87. * The package contents are fixed up to be usable to create a Package
  88. * object by providing dummy "name" and "version" values if they have not
  89. * been provided in the file. This is consistent with the default root
  90. * package loading behavior of Composer.
  91. *
  92. * @param string $path
  93. * @return array
  94. */
  95. protected function readPackageJson($path)
  96. {
  97. $file = new JsonFile($path);
  98. $json = $file->read();
  99. if (!isset($json['name'])) {
  100. $json['name'] = 'merge-plugin/' .
  101. strtr($path, DIRECTORY_SEPARATOR, '-');
  102. }
  103. if (!isset($json['version'])) {
  104. $json['version'] = '1.0.0';
  105. }
  106. return $json;
  107. }
  108. /**
  109. * @return CompletePackage
  110. */
  111. protected function loadPackage($json)
  112. {
  113. $loader = new ArrayLoader();
  114. $package = $loader->load($json);
  115. // @codeCoverageIgnoreStart
  116. if (!$package instanceof CompletePackage) {
  117. throw new UnexpectedValueException(
  118. 'Expected instance of CompletePackage, got ' .
  119. get_class($package)
  120. );
  121. }
  122. // @codeCoverageIgnoreEnd
  123. return $package;
  124. }
  125. /**
  126. * Merge this package into a RootPackageInterface
  127. *
  128. * @param RootPackageInterface $root
  129. * @param PluginState $state
  130. */
  131. public function mergeInto(RootPackageInterface $root, PluginState $state)
  132. {
  133. $this->addRepositories($root);
  134. $this->mergeRequires('require', $root, $state);
  135. if ($state->isDevMode()) {
  136. $this->mergeRequires('require-dev', $root, $state);
  137. }
  138. $this->mergePackageLinks('conflict', $root);
  139. $this->mergePackageLinks('replace', $root);
  140. $this->mergePackageLinks('provide', $root);
  141. $this->mergeSuggests($root);
  142. $this->mergeAutoload('autoload', $root);
  143. if ($state->isDevMode()) {
  144. $this->mergeAutoload('devAutoload', $root);
  145. }
  146. $this->mergeExtra($root, $state);
  147. }
  148. /**
  149. * Add a collection of repositories described by the given configuration
  150. * to the given package and the global repository manager.
  151. *
  152. * @param RootPackageInterface $root
  153. */
  154. protected function addRepositories(RootPackageInterface $root)
  155. {
  156. if (!isset($this->json['repositories'])) {
  157. return;
  158. }
  159. $repoManager = $this->composer->getRepositoryManager();
  160. $newRepos = array();
  161. foreach ($this->json['repositories'] as $repoJson) {
  162. if (!isset($repoJson['type'])) {
  163. continue;
  164. }
  165. $this->logger->info("Adding {$repoJson['type']} repository");
  166. $repo = $repoManager->createRepository(
  167. $repoJson['type'],
  168. $repoJson
  169. );
  170. $repoManager->addRepository($repo);
  171. $newRepos[] = $repo;
  172. }
  173. $unwrapped = self::unwrapIfNeeded($root, 'setRepositories');
  174. $unwrapped->setRepositories(array_merge(
  175. $newRepos,
  176. $root->getRepositories()
  177. ));
  178. }
  179. /**
  180. * Merge require or require-dev into a RootPackageInterface
  181. *
  182. * @param string $type 'require' or 'require-dev'
  183. * @param RootPackageInterface $root
  184. * @param PluginState $state
  185. */
  186. protected function mergeRequires(
  187. $type,
  188. RootPackageInterface $root,
  189. PluginState $state
  190. ) {
  191. $linkType = BasePackage::$supportedLinkTypes[$type];
  192. $getter = 'get' . ucfirst($linkType['method']);
  193. $setter = 'set' . ucfirst($linkType['method']);
  194. $requires = $this->package->{$getter}();
  195. if (empty($requires)) {
  196. return;
  197. }
  198. $this->mergeStabilityFlags($root, $requires);
  199. $requires = $this->replaceSelfVersionDependencies(
  200. $type,
  201. $requires,
  202. $root
  203. );
  204. $root->{$setter}($this->mergeOrDefer(
  205. $type,
  206. $root->{$getter}(),
  207. $requires,
  208. $state
  209. ));
  210. }
  211. /**
  212. * Merge two collections of package links and collect duplicates for
  213. * subsequent processing.
  214. *
  215. * @param string $type 'require' or 'require-dev'
  216. * @param array $origin Primary collection
  217. * @param array $merge Additional collection
  218. * @param PluginState $state
  219. * @return array Merged collection
  220. */
  221. protected function mergeOrDefer(
  222. $type,
  223. array $origin,
  224. array $merge,
  225. $state
  226. ) {
  227. $dups = array();
  228. foreach ($merge as $name => $link) {
  229. if (!isset($origin[$name]) || $state->replaceDuplicateLinks()) {
  230. $this->logger->info("Merging <comment>{$name}</comment>");
  231. $origin[$name] = $link;
  232. } else {
  233. // Defer to solver.
  234. $this->logger->info(
  235. "Deferring duplicate <comment>{$name}</comment>"
  236. );
  237. $dups[] = $link;
  238. }
  239. }
  240. $state->addDuplicateLinks($type, $dups);
  241. return $origin;
  242. }
  243. /**
  244. * Merge autoload or autoload-dev into a RootPackageInterface
  245. *
  246. * @param string $type 'autoload' or 'devAutoload'
  247. * @param RootPackageInterface $root
  248. */
  249. protected function mergeAutoload($type, RootPackageInterface $root)
  250. {
  251. $getter = 'get' . ucfirst($type);
  252. $setter = 'set' . ucfirst($type);
  253. $autoload = $this->package->{$getter}();
  254. if (empty($autoload)) {
  255. return;
  256. }
  257. $unwrapped = self::unwrapIfNeeded($root, $setter);
  258. $unwrapped->{$setter}(array_merge_recursive(
  259. $root->{$getter}(),
  260. $this->fixRelativePaths($autoload)
  261. ));
  262. }
  263. /**
  264. * Fix a collection of paths that are relative to this package to be
  265. * relative to the base package.
  266. *
  267. * @param array $paths
  268. * @return array
  269. */
  270. protected function fixRelativePaths(array $paths)
  271. {
  272. $base = dirname($this->path);
  273. $base = ($base === '.') ? '' : "{$base}/";
  274. array_walk_recursive(
  275. $paths,
  276. function (&$path) use ($base) {
  277. $path = "{$base}{$path}";
  278. }
  279. );
  280. return $paths;
  281. }
  282. /**
  283. * Extract and merge stability flags from the given collection of
  284. * requires and merge them into a RootPackageInterface
  285. *
  286. * @param RootPackageInterface $root
  287. * @param array $requires
  288. */
  289. protected function mergeStabilityFlags(
  290. RootPackageInterface $root,
  291. array $requires
  292. ) {
  293. $flags = $root->getStabilityFlags();
  294. $sf = new StabilityFlags($flags, $root->getMinimumStability());
  295. $unwrapped = self::unwrapIfNeeded($root, 'setStabilityFlags');
  296. $unwrapped->setStabilityFlags(array_merge(
  297. $flags,
  298. $sf->extractAll($requires)
  299. ));
  300. }
  301. /**
  302. * Merge package links of the given type into a RootPackageInterface
  303. *
  304. * @param string $type 'conflict', 'replace' or 'provide'
  305. * @param RootPackageInterface $root
  306. */
  307. protected function mergePackageLinks($type, RootPackageInterface $root)
  308. {
  309. $linkType = BasePackage::$supportedLinkTypes[$type];
  310. $getter = 'get' . ucfirst($linkType['method']);
  311. $setter = 'set' . ucfirst($linkType['method']);
  312. $links = $this->package->{$getter}();
  313. if (!empty($links)) {
  314. $unwrapped = self::unwrapIfNeeded($root, $setter);
  315. if ($root !== $unwrapped) {
  316. $this->logger->warning(
  317. 'This Composer version does not support ' .
  318. "'{$type}' merging for aliased packages."
  319. );
  320. }
  321. $unwrapped->{$setter}(array_merge(
  322. $root->{$getter}(),
  323. $this->replaceSelfVersionDependencies($type, $links, $root)
  324. ));
  325. }
  326. }
  327. /**
  328. * Merge suggested packages into a RootPackageInterface
  329. *
  330. * @param RootPackageInterface $root
  331. */
  332. protected function mergeSuggests(RootPackageInterface $root)
  333. {
  334. $suggests = $this->package->getSuggests();
  335. if (!empty($suggests)) {
  336. $unwrapped = self::unwrapIfNeeded($root, 'setSuggests');
  337. $unwrapped->setSuggests(array_merge(
  338. $root->getSuggests(),
  339. $suggests
  340. ));
  341. }
  342. }
  343. /**
  344. * Merge extra config into a RootPackageInterface
  345. *
  346. * @param RootPackageInterface $root
  347. * @param PluginState $state
  348. */
  349. public function mergeExtra(RootPackageInterface $root, PluginState $state)
  350. {
  351. $extra = $this->package->getExtra();
  352. unset($extra['merge-plugin']);
  353. if (!$state->shouldMergeExtra() || empty($extra)) {
  354. return;
  355. }
  356. $rootExtra = $root->getExtra();
  357. $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
  358. if ($state->replaceDuplicateLinks()) {
  359. $unwrapped->setExtra(
  360. array_merge($rootExtra, $extra)
  361. );
  362. } else {
  363. foreach (array_intersect(
  364. array_keys($extra),
  365. array_keys($rootExtra)
  366. ) as $key) {
  367. $this->logger->info(
  368. "Ignoring duplicate <comment>{$key}</comment> in ".
  369. "<comment>{$this->path}</comment> extra config."
  370. );
  371. }
  372. $unwrapped->setExtra(
  373. array_merge($extra, $rootExtra)
  374. );
  375. }
  376. }
  377. /**
  378. * Update Links with a 'self.version' constraint with the root package's
  379. * version.
  380. *
  381. * @param string $type Link type
  382. * @param array $links
  383. * @param RootPackageInterface $root
  384. * @return array
  385. */
  386. protected function replaceSelfVersionDependencies(
  387. $type,
  388. array $links,
  389. RootPackageInterface $root
  390. ) {
  391. $linkType = BasePackage::$supportedLinkTypes[$type];
  392. $version = $root->getVersion();
  393. $prettyVersion = $root->getPrettyVersion();
  394. $vp = new VersionParser();
  395. return array_map(
  396. function ($link) use ($linkType, $version, $prettyVersion, $vp) {
  397. if ('self.version' === $link->getPrettyConstraint()) {
  398. return new Link(
  399. $link->getSource(),
  400. $link->getTarget(),
  401. $vp->parseConstraints($version),
  402. $linkType['description'],
  403. $prettyVersion
  404. );
  405. }
  406. return $link;
  407. },
  408. $links
  409. );
  410. }
  411. /**
  412. * Get a full featured Package from a RootPackageInterface.
  413. *
  414. * In Composer versions before 599ad77 the RootPackageInterface only
  415. * defines a sub-set of operations needed by composer-merge-plugin and
  416. * RootAliasPackage only implemented those methods defined by the
  417. * interface. Most of the unimplemented methods in RootAliasPackage can be
  418. * worked around because the getter methods that are implemented proxy to
  419. * the aliased package which we can modify by unwrapping. The exception
  420. * being modifying the 'conflicts', 'provides' and 'replaces' collections.
  421. * We have no way to actually modify those collections unfortunately in
  422. * older versions of Composer.
  423. *
  424. * @param RootPackageInterface $root
  425. * @param string $method Method needed
  426. * @return RootPackageInterface|RootPackage
  427. */
  428. public static function unwrapIfNeeded(
  429. RootPackageInterface $root,
  430. $method = 'setExtra'
  431. ) {
  432. if ($root instanceof RootAliasPackage &&
  433. !method_exists($root, $method)
  434. ) {
  435. // Unwrap and return the aliased RootPackage.
  436. $root = $root->getAliasOf();
  437. }
  438. return $root;
  439. }
  440. }
  441. // vim:sw=4:ts=4:sts=4:et: