summaryrefslogtreecommitdiff
path: root/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php')
-rw-r--r--vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php487
1 files changed, 487 insertions, 0 deletions
diff --git a/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php b/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
new file mode 100644
index 00000000..ebecdff5
--- /dev/null
+++ b/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
@@ -0,0 +1,487 @@
+<?php
+/**
+ * This file is part of the Composer Merge plugin.
+ *
+ * Copyright (C) 2015 Bryan Davis, Wikimedia Foundation, and contributors
+ *
+ * This software may be modified and distributed under the terms of the MIT
+ * license. See the LICENSE file for details.
+ */
+
+namespace Wikimedia\Composer\Merge;
+
+use Wikimedia\Composer\Logger;
+
+use Composer\Composer;
+use Composer\Json\JsonFile;
+use Composer\Package\BasePackage;
+use Composer\Package\CompletePackage;
+use Composer\Package\Link;
+use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\RootAliasPackage;
+use Composer\Package\RootPackage;
+use Composer\Package\RootPackageInterface;
+use Composer\Package\Version\VersionParser;
+use UnexpectedValueException;
+
+/**
+ * Processing for a composer.json file that will be merged into
+ * a RootPackageInterface
+ *
+ * @author Bryan Davis <bd808@bd808.com>
+ */
+class ExtraPackage
+{
+
+ /**
+ * @var Composer $composer
+ */
+ protected $composer;
+
+ /**
+ * @var Logger $logger
+ */
+ protected $logger;
+
+ /**
+ * @var string $path
+ */
+ protected $path;
+
+ /**
+ * @var array $json
+ */
+ protected $json;
+
+ /**
+ * @var CompletePackage $package
+ */
+ protected $package;
+
+ /**
+ * @param string $path Path to composer.json file
+ * @param Composer $composer
+ * @param Logger $logger
+ */
+ public function __construct($path, Composer $composer, Logger $logger)
+ {
+ $this->path = $path;
+ $this->composer = $composer;
+ $this->logger = $logger;
+ $this->json = $this->readPackageJson($path);
+ $this->package = $this->loadPackage($this->json);
+ }
+
+ /**
+ * Get list of additional packages to include if precessing recursively.
+ *
+ * @return array
+ */
+ public function getIncludes()
+ {
+ return isset($this->json['extra']['merge-plugin']['include']) ?
+ $this->json['extra']['merge-plugin']['include'] : array();
+ }
+
+ /**
+ * Get list of additional packages to require if precessing recursively.
+ *
+ * @return array
+ */
+ public function getRequires()
+ {
+ return isset($this->json['extra']['merge-plugin']['require']) ?
+ $this->json['extra']['merge-plugin']['require'] : array();
+ }
+
+ /**
+ * Read the contents of a composer.json style file into an array.
+ *
+ * The package contents are fixed up to be usable to create a Package
+ * object by providing dummy "name" and "version" values if they have not
+ * been provided in the file. This is consistent with the default root
+ * package loading behavior of Composer.
+ *
+ * @param string $path
+ * @return array
+ */
+ protected function readPackageJson($path)
+ {
+ $file = new JsonFile($path);
+ $json = $file->read();
+ if (!isset($json['name'])) {
+ $json['name'] = 'merge-plugin/' .
+ strtr($path, DIRECTORY_SEPARATOR, '-');
+ }
+ if (!isset($json['version'])) {
+ $json['version'] = '1.0.0';
+ }
+ return $json;
+ }
+
+ /**
+ * @return CompletePackage
+ */
+ protected function loadPackage($json)
+ {
+ $loader = new ArrayLoader();
+ $package = $loader->load($json);
+ // @codeCoverageIgnoreStart
+ if (!$package instanceof CompletePackage) {
+ throw new UnexpectedValueException(
+ 'Expected instance of CompletePackage, got ' .
+ get_class($package)
+ );
+ }
+ // @codeCoverageIgnoreEnd
+ return $package;
+ }
+
+ /**
+ * Merge this package into a RootPackageInterface
+ *
+ * @param RootPackageInterface $root
+ * @param PluginState $state
+ */
+ public function mergeInto(RootPackageInterface $root, PluginState $state)
+ {
+ $this->addRepositories($root);
+
+ $this->mergeRequires('require', $root, $state);
+ if ($state->isDevMode()) {
+ $this->mergeRequires('require-dev', $root, $state);
+ }
+
+ $this->mergePackageLinks('conflict', $root);
+ $this->mergePackageLinks('replace', $root);
+ $this->mergePackageLinks('provide', $root);
+
+ $this->mergeSuggests($root);
+
+ $this->mergeAutoload('autoload', $root);
+ if ($state->isDevMode()) {
+ $this->mergeAutoload('devAutoload', $root);
+ }
+
+ $this->mergeExtra($root, $state);
+ }
+
+ /**
+ * Add a collection of repositories described by the given configuration
+ * to the given package and the global repository manager.
+ *
+ * @param RootPackageInterface $root
+ */
+ protected function addRepositories(RootPackageInterface $root)
+ {
+ if (!isset($this->json['repositories'])) {
+ return;
+ }
+ $repoManager = $this->composer->getRepositoryManager();
+ $newRepos = array();
+
+ foreach ($this->json['repositories'] as $repoJson) {
+ if (!isset($repoJson['type'])) {
+ continue;
+ }
+ $this->logger->info("Adding {$repoJson['type']} repository");
+ $repo = $repoManager->createRepository(
+ $repoJson['type'],
+ $repoJson
+ );
+ $repoManager->addRepository($repo);
+ $newRepos[] = $repo;
+ }
+
+ $unwrapped = self::unwrapIfNeeded($root, 'setRepositories');
+ $unwrapped->setRepositories(array_merge(
+ $newRepos,
+ $root->getRepositories()
+ ));
+ }
+
+ /**
+ * Merge require or require-dev into a RootPackageInterface
+ *
+ * @param string $type 'require' or 'require-dev'
+ * @param RootPackageInterface $root
+ * @param PluginState $state
+ */
+ protected function mergeRequires(
+ $type,
+ RootPackageInterface $root,
+ PluginState $state
+ ) {
+ $linkType = BasePackage::$supportedLinkTypes[$type];
+ $getter = 'get' . ucfirst($linkType['method']);
+ $setter = 'set' . ucfirst($linkType['method']);
+
+ $requires = $this->package->{$getter}();
+ if (empty($requires)) {
+ return;
+ }
+
+ $this->mergeStabilityFlags($root, $requires);
+
+ $requires = $this->replaceSelfVersionDependencies(
+ $type,
+ $requires,
+ $root
+ );
+
+ $root->{$setter}($this->mergeOrDefer(
+ $type,
+ $root->{$getter}(),
+ $requires,
+ $state
+ ));
+ }
+
+ /**
+ * Merge two collections of package links and collect duplicates for
+ * subsequent processing.
+ *
+ * @param string $type 'require' or 'require-dev'
+ * @param array $origin Primary collection
+ * @param array $merge Additional collection
+ * @param PluginState $state
+ * @return array Merged collection
+ */
+ protected function mergeOrDefer(
+ $type,
+ array $origin,
+ array $merge,
+ $state
+ ) {
+ $dups = array();
+ foreach ($merge as $name => $link) {
+ if (!isset($origin[$name]) || $state->replaceDuplicateLinks()) {
+ $this->logger->info("Merging <comment>{$name}</comment>");
+ $origin[$name] = $link;
+ } else {
+ // Defer to solver.
+ $this->logger->info(
+ "Deferring duplicate <comment>{$name}</comment>"
+ );
+ $dups[] = $link;
+ }
+ }
+ $state->addDuplicateLinks($type, $dups);
+ return $origin;
+ }
+
+ /**
+ * Merge autoload or autoload-dev into a RootPackageInterface
+ *
+ * @param string $type 'autoload' or 'devAutoload'
+ * @param RootPackageInterface $root
+ */
+ protected function mergeAutoload($type, RootPackageInterface $root)
+ {
+ $getter = 'get' . ucfirst($type);
+ $setter = 'set' . ucfirst($type);
+
+ $autoload = $this->package->{$getter}();
+ if (empty($autoload)) {
+ return;
+ }
+
+ $unwrapped = self::unwrapIfNeeded($root, $setter);
+ $unwrapped->{$setter}(array_merge_recursive(
+ $root->{$getter}(),
+ $this->fixRelativePaths($autoload)
+ ));
+ }
+
+ /**
+ * Fix a collection of paths that are relative to this package to be
+ * relative to the base package.
+ *
+ * @param array $paths
+ * @return array
+ */
+ protected function fixRelativePaths(array $paths)
+ {
+ $base = dirname($this->path);
+ $base = ($base === '.') ? '' : "{$base}/";
+
+ array_walk_recursive(
+ $paths,
+ function (&$path) use ($base) {
+ $path = "{$base}{$path}";
+ }
+ );
+ return $paths;
+ }
+
+ /**
+ * Extract and merge stability flags from the given collection of
+ * requires and merge them into a RootPackageInterface
+ *
+ * @param RootPackageInterface $root
+ * @param array $requires
+ */
+ protected function mergeStabilityFlags(
+ RootPackageInterface $root,
+ array $requires
+ ) {
+ $flags = $root->getStabilityFlags();
+ $sf = new StabilityFlags($flags, $root->getMinimumStability());
+
+ $unwrapped = self::unwrapIfNeeded($root, 'setStabilityFlags');
+ $unwrapped->setStabilityFlags(array_merge(
+ $flags,
+ $sf->extractAll($requires)
+ ));
+ }
+
+ /**
+ * Merge package links of the given type into a RootPackageInterface
+ *
+ * @param string $type 'conflict', 'replace' or 'provide'
+ * @param RootPackageInterface $root
+ */
+ protected function mergePackageLinks($type, RootPackageInterface $root)
+ {
+ $linkType = BasePackage::$supportedLinkTypes[$type];
+ $getter = 'get' . ucfirst($linkType['method']);
+ $setter = 'set' . ucfirst($linkType['method']);
+
+ $links = $this->package->{$getter}();
+ if (!empty($links)) {
+ $unwrapped = self::unwrapIfNeeded($root, $setter);
+ if ($root !== $unwrapped) {
+ $this->logger->warning(
+ 'This Composer version does not support ' .
+ "'{$type}' merging for aliased packages."
+ );
+ }
+ $unwrapped->{$setter}(array_merge(
+ $root->{$getter}(),
+ $this->replaceSelfVersionDependencies($type, $links, $root)
+ ));
+ }
+ }
+
+ /**
+ * Merge suggested packages into a RootPackageInterface
+ *
+ * @param RootPackageInterface $root
+ */
+ protected function mergeSuggests(RootPackageInterface $root)
+ {
+ $suggests = $this->package->getSuggests();
+ if (!empty($suggests)) {
+ $unwrapped = self::unwrapIfNeeded($root, 'setSuggests');
+ $unwrapped->setSuggests(array_merge(
+ $root->getSuggests(),
+ $suggests
+ ));
+ }
+ }
+
+ /**
+ * Merge extra config into a RootPackageInterface
+ *
+ * @param RootPackageInterface $root
+ * @param PluginState $state
+ */
+ public function mergeExtra(RootPackageInterface $root, PluginState $state)
+ {
+ $extra = $this->package->getExtra();
+ unset($extra['merge-plugin']);
+ if (!$state->shouldMergeExtra() || empty($extra)) {
+ return;
+ }
+
+ $rootExtra = $root->getExtra();
+ $unwrapped = self::unwrapIfNeeded($root, 'setExtra');
+
+ if ($state->replaceDuplicateLinks()) {
+ $unwrapped->setExtra(
+ array_merge($rootExtra, $extra)
+ );
+
+ } else {
+ foreach (array_intersect(
+ array_keys($extra),
+ array_keys($rootExtra)
+ ) as $key) {
+ $this->logger->info(
+ "Ignoring duplicate <comment>{$key}</comment> in ".
+ "<comment>{$this->path}</comment> extra config."
+ );
+ }
+ $unwrapped->setExtra(
+ array_merge($extra, $rootExtra)
+ );
+ }
+ }
+
+ /**
+ * Update Links with a 'self.version' constraint with the root package's
+ * version.
+ *
+ * @param string $type Link type
+ * @param array $links
+ * @param RootPackageInterface $root
+ * @return array
+ */
+ protected function replaceSelfVersionDependencies(
+ $type,
+ array $links,
+ RootPackageInterface $root
+ ) {
+ $linkType = BasePackage::$supportedLinkTypes[$type];
+ $version = $root->getVersion();
+ $prettyVersion = $root->getPrettyVersion();
+ $vp = new VersionParser();
+
+ return array_map(
+ function ($link) use ($linkType, $version, $prettyVersion, $vp) {
+ if ('self.version' === $link->getPrettyConstraint()) {
+ return new Link(
+ $link->getSource(),
+ $link->getTarget(),
+ $vp->parseConstraints($version),
+ $linkType['description'],
+ $prettyVersion
+ );
+ }
+ return $link;
+ },
+ $links
+ );
+ }
+
+ /**
+ * Get a full featured Package from a RootPackageInterface.
+ *
+ * In Composer versions before 599ad77 the RootPackageInterface only
+ * defines a sub-set of operations needed by composer-merge-plugin and
+ * RootAliasPackage only implemented those methods defined by the
+ * interface. Most of the unimplemented methods in RootAliasPackage can be
+ * worked around because the getter methods that are implemented proxy to
+ * the aliased package which we can modify by unwrapping. The exception
+ * being modifying the 'conflicts', 'provides' and 'replaces' collections.
+ * We have no way to actually modify those collections unfortunately in
+ * older versions of Composer.
+ *
+ * @param RootPackageInterface $root
+ * @param string $method Method needed
+ * @return RootPackageInterface|RootPackage
+ */
+ public static function unwrapIfNeeded(
+ RootPackageInterface $root,
+ $method = 'setExtra'
+ ) {
+ if ($root instanceof RootAliasPackage &&
+ !method_exists($root, $method)
+ ) {
+ // Unwrap and return the aliased RootPackage.
+ $root = $root->getAliasOf();
+ }
+ return $root;
+ }
+}
+// vim:sw=4:ts=4:sts=4:et: