Mercurial > hg > cmmr2012-drupal-site
diff vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php Thu Jul 05 14:24:15 2018 +0000 @@ -0,0 +1,381 @@ +<?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; + +use Wikimedia\Composer\Merge\ExtraPackage; +use Wikimedia\Composer\Merge\MissingFileException; +use Wikimedia\Composer\Merge\PluginState; + +use Composer\Composer; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\EventDispatcher\Event as BaseEvent; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Factory; +use Composer\Installer; +use Composer\Installer\InstallerEvent; +use Composer\Installer\InstallerEvents; +use Composer\Installer\PackageEvent; +use Composer\Installer\PackageEvents; +use Composer\IO\IOInterface; +use Composer\Package\RootPackageInterface; +use Composer\Plugin\PluginInterface; +use Composer\Script\Event as ScriptEvent; +use Composer\Script\ScriptEvents; + +/** + * Composer plugin that allows merging multiple composer.json files. + * + * When installed, this plugin will look for a "merge-plugin" key in the + * composer configuration's "extra" section. The value for this key is + * a set of options configuring the plugin. + * + * An "include" setting is required. The value of this setting can be either + * a single value or an array of values. Each value is treated as a glob() + * pattern identifying additional composer.json style configuration files to + * merge into the configuration for the current compser execution. + * + * The "autoload", "autoload-dev", "conflict", "provide", "replace", + * "repositories", "require", "require-dev", and "suggest" sections of the + * found configuration files will be merged into the root package + * configuration as though they were directly included in the top-level + * composer.json file. + * + * If included files specify conflicting package versions for "require" or + * "require-dev", the normal Composer dependency solver process will be used + * to attempt to resolve the conflict. Specifying the 'replace' key as true will + * change this default behaviour so that the last-defined version of a package + * will win, allowing for force-overrides of package defines. + * + * By default the "extra" section is not merged. This can be enabled by + * setitng the 'merge-extra' key to true. In normal mode, when the same key is + * found in both the original and the imported extra section, the version in + * the original config is used and the imported version is skipped. If + * 'replace' mode is active, this behaviour changes so the imported version of + * the key is used, replacing the version in the original config. + * + * + * @code + * { + * "require": { + * "wikimedia/composer-merge-plugin": "dev-master" + * }, + * "extra": { + * "merge-plugin": { + * "include": [ + * "composer.local.json" + * ] + * } + * } + * } + * @endcode + * + * @author Bryan Davis <bd808@bd808.com> + */ +class MergePlugin implements PluginInterface, EventSubscriberInterface +{ + + /** + * Offical package name + */ + const PACKAGE_NAME = 'wikimedia/composer-merge-plugin'; + + /** + * Name of the composer 1.1 init event. + */ + const COMPAT_PLUGINEVENTS_INIT = 'init'; + + /** + * Priority that plugin uses to register callbacks. + */ + const CALLBACK_PRIORITY = 50000; + + /** + * @var Composer $composer + */ + protected $composer; + + /** + * @var PluginState $state + */ + protected $state; + + /** + * @var Logger $logger + */ + protected $logger; + + /** + * Files that have already been fully processed + * + * @var string[] $loaded + */ + protected $loaded = array(); + + /** + * Files that have already been partially processed + * + * @var string[] $loadedNoDev + */ + protected $loadedNoDev = array(); + + /** + * {@inheritdoc} + */ + public function activate(Composer $composer, IOInterface $io) + { + $this->composer = $composer; + $this->state = new PluginState($this->composer); + $this->logger = new Logger('merge-plugin', $io); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + // Use our own constant to make this event optional. Once + // composer-1.1 is required, this can use PluginEvents::INIT + // instead. + self::COMPAT_PLUGINEVENTS_INIT => + array('onInit', self::CALLBACK_PRIORITY), + InstallerEvents::PRE_DEPENDENCIES_SOLVING => + array('onDependencySolve', self::CALLBACK_PRIORITY), + PackageEvents::POST_PACKAGE_INSTALL => + array('onPostPackageInstall', self::CALLBACK_PRIORITY), + ScriptEvents::POST_INSTALL_CMD => + array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY), + ScriptEvents::POST_UPDATE_CMD => + array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY), + ScriptEvents::PRE_AUTOLOAD_DUMP => + array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), + ScriptEvents::PRE_INSTALL_CMD => + array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), + ScriptEvents::PRE_UPDATE_CMD => + array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), + ); + } + + /** + * Handle an event callback for initialization. + * + * @param \Composer\EventDispatcher\Event $event + */ + public function onInit(BaseEvent $event) + { + $this->state->loadSettings(); + // It is not possible to know if the user specified --dev or --no-dev + // so assume it is false. The dev section will be merged later when + // the other events fire. + $this->state->setDevMode(false); + $this->mergeFiles($this->state->getIncludes(), false); + $this->mergeFiles($this->state->getRequires(), true); + } + + /** + * Handle an event callback for an install, update or dump command by + * checking for "merge-plugin" in the "extra" data and merging package + * contents if found. + * + * @param ScriptEvent $event + */ + public function onInstallUpdateOrDump(ScriptEvent $event) + { + $this->state->loadSettings(); + $this->state->setDevMode($event->isDevMode()); + $this->mergeFiles($this->state->getIncludes(), false); + $this->mergeFiles($this->state->getRequires(), true); + + if ($event->getName() === ScriptEvents::PRE_AUTOLOAD_DUMP) { + $this->state->setDumpAutoloader(true); + $flags = $event->getFlags(); + if (isset($flags['optimize'])) { + $this->state->setOptimizeAutoloader($flags['optimize']); + } + } + } + + /** + * Find configuration files matching the configured glob patterns and + * merge their contents with the master package. + * + * @param array $patterns List of files/glob patterns + * @param bool $required Are the patterns required to match files? + * @throws MissingFileException when required and a pattern returns no + * results + */ + protected function mergeFiles(array $patterns, $required = false) + { + $root = $this->composer->getPackage(); + + $files = array_map( + function ($files, $pattern) use ($required) { + if ($required && !$files) { + throw new MissingFileException( + "merge-plugin: No files matched required '{$pattern}'" + ); + } + return $files; + }, + array_map('glob', $patterns), + $patterns + ); + + foreach (array_reduce($files, 'array_merge', array()) as $path) { + $this->mergeFile($root, $path); + } + } + + /** + * Read a JSON file and merge its contents + * + * @param RootPackageInterface $root + * @param string $path + */ + protected function mergeFile(RootPackageInterface $root, $path) + { + if (isset($this->loaded[$path]) || + (isset($this->loadedNoDev[$path]) && !$this->state->isDevMode()) + ) { + $this->logger->debug( + "Already merged <comment>$path</comment> completely" + ); + return; + } + + $package = new ExtraPackage($path, $this->composer, $this->logger); + + if (isset($this->loadedNoDev[$path])) { + $this->logger->info( + "Loading -dev sections of <comment>{$path}</comment>..." + ); + $package->mergeDevInto($root, $this->state); + } else { + $this->logger->info("Loading <comment>{$path}</comment>..."); + $package->mergeInto($root, $this->state); + } + + if ($this->state->isDevMode()) { + $this->loaded[$path] = true; + } else { + $this->loadedNoDev[$path] = true; + } + + if ($this->state->recurseIncludes()) { + $this->mergeFiles($package->getIncludes(), false); + $this->mergeFiles($package->getRequires(), true); + } + } + + /** + * Handle an event callback for pre-dependency solving phase of an install + * or update by adding any duplicate package dependencies found during + * initial merge processing to the request that will be processed by the + * dependency solver. + * + * @param InstallerEvent $event + */ + public function onDependencySolve(InstallerEvent $event) + { + $request = $event->getRequest(); + foreach ($this->state->getDuplicateLinks('require') as $link) { + $this->logger->info( + "Adding dependency <comment>{$link}</comment>" + ); + $request->install($link->getTarget(), $link->getConstraint()); + } + + // Issue #113: Check devMode of event rather than our global state. + // Composer fires the PRE_DEPENDENCIES_SOLVING event twice for + // `--no-dev` operations to decide which packages are dev only + // requirements. + if ($this->state->shouldMergeDev() && $event->isDevMode()) { + foreach ($this->state->getDuplicateLinks('require-dev') as $link) { + $this->logger->info( + "Adding dev dependency <comment>{$link}</comment>" + ); + $request->install($link->getTarget(), $link->getConstraint()); + } + } + } + + /** + * Handle an event callback following installation of a new package by + * checking to see if the package that was installed was our plugin. + * + * @param PackageEvent $event + */ + public function onPostPackageInstall(PackageEvent $event) + { + $op = $event->getOperation(); + if ($op instanceof InstallOperation) { + $package = $op->getPackage()->getName(); + if ($package === self::PACKAGE_NAME) { + $this->logger->info('composer-merge-plugin installed'); + $this->state->setFirstInstall(true); + $this->state->setLocked( + $event->getComposer()->getLocker()->isLocked() + ); + } + } + } + + /** + * Handle an event callback following an install or update command. If our + * plugin was installed during the run then trigger an update command to + * process any merge-patterns in the current config. + * + * @param ScriptEvent $event + */ + public function onPostInstallOrUpdate(ScriptEvent $event) + { + // @codeCoverageIgnoreStart + if ($this->state->isFirstInstall()) { + $this->state->setFirstInstall(false); + $this->logger->info( + '<comment>' . + 'Running additional update to apply merge settings' . + '</comment>' + ); + + $config = $this->composer->getConfig(); + + $preferSource = $config->get('preferred-install') == 'source'; + $preferDist = $config->get('preferred-install') == 'dist'; + + $installer = Installer::create( + $event->getIO(), + // Create a new Composer instance to ensure full processing of + // the merged files. + Factory::create($event->getIO(), null, false) + ); + + $installer->setPreferSource($preferSource); + $installer->setPreferDist($preferDist); + $installer->setDevMode($event->isDevMode()); + $installer->setDumpAutoloader($this->state->shouldDumpAutoloader()); + $installer->setOptimizeAutoloader( + $this->state->shouldOptimizeAutoloader() + ); + + if ($this->state->forceUpdate()) { + // Force update mode so that new packages are processed rather + // than just telling the user that composer.json and + // composer.lock don't match. + $installer->setUpdate(true); + } + + $installer->run(); + } + // @codeCoverageIgnoreEnd + } +} +// vim:sw=4:ts=4:sts=4:et: