View source
<?php
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;
class ExtraPackage {
protected $composer;
protected $logger;
protected $path;
protected $json;
protected $package;
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);
}
public function getIncludes() {
return isset($this->json['extra']['merge-plugin']['include']) ? $this->json['extra']['merge-plugin']['include'] : array();
}
public function getRequires() {
return isset($this->json['extra']['merge-plugin']['require']) ? $this->json['extra']['merge-plugin']['require'] : 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;
}
protected function loadPackage($json) {
$loader = new ArrayLoader();
$package = $loader
->load($json);
if (!$package instanceof CompletePackage) {
throw new UnexpectedValueException('Expected instance of CompletePackage, got ' . get_class($package));
}
return $package;
}
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);
}
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()));
}
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));
}
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 {
$this->logger
->info("Deferring duplicate <comment>{$name}</comment>");
$dups[] = $link;
}
}
$state
->addDuplicateLinks($type, $dups);
return $origin;
}
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)));
}
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;
}
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)));
}
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)));
}
}
protected function mergeSuggests(RootPackageInterface $root) {
$suggests = $this->package
->getSuggests();
if (!empty($suggests)) {
$unwrapped = self::unwrapIfNeeded($root, 'setSuggests');
$unwrapped
->setSuggests(array_merge($root
->getSuggests(), $suggests));
}
}
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));
}
}
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);
}
public static function unwrapIfNeeded(RootPackageInterface $root, $method = 'setExtra') {
if ($root instanceof RootAliasPackage && !method_exists($root, $method)) {
$root = $root
->getAliasOf();
}
return $root;
}
}