diff options
author | Andrew Dolgov <[email protected]> | 2023-10-20 17:12:29 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2023-10-20 21:13:39 +0300 |
commit | cdd7ad020e165fe680703b6d3319b908b682fb7a (patch) | |
tree | b51eb09b7b4587e8fbc5624ac8d88d28cfcd0b04 /vendor/php-http/discovery/src/Composer/Plugin.php | |
parent | 45a9ff0c88cbd33892ff16ab837e9059937d656e (diff) |
jaeger-client -> opentelemetry
Diffstat (limited to 'vendor/php-http/discovery/src/Composer/Plugin.php')
-rw-r--r-- | vendor/php-http/discovery/src/Composer/Plugin.php | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/vendor/php-http/discovery/src/Composer/Plugin.php b/vendor/php-http/discovery/src/Composer/Plugin.php new file mode 100644 index 000000000..32ef401e3 --- /dev/null +++ b/vendor/php-http/discovery/src/Composer/Plugin.php @@ -0,0 +1,465 @@ +<?php + +namespace Http\Discovery\Composer; + +use Composer\Composer; +use Composer\DependencyResolver\Pool; +use Composer\EventDispatcher\EventSubscriberInterface; +use Composer\Factory; +use Composer\Installer; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Json\JsonManipulator; +use Composer\Package\Locker; +use Composer\Package\Version\VersionParser; +use Composer\Package\Version\VersionSelector; +use Composer\Plugin\PluginInterface; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\RepositorySet; +use Composer\Script\Event; +use Composer\Script\ScriptEvents; +use Composer\Util\Filesystem; +use Http\Discovery\ClassDiscovery; + +/** + * Auto-installs missing implementations. + * + * When a dependency requires both this package and one of the supported `*-implementation` + * virtual packages, this plugin will auto-install a well-known implementation if none is + * found. The plugin will first look at already installed packages and figure out the + * preferred implementation to install based on the below stickyness rules (or on the first + * listed implementation if no rules match.) + * + * Don't miss updating src/Strategy/Common*Strategy.php when adding a new supported package. + * + * @author Nicolas Grekas <[email protected]> + * + * @internal + */ +class Plugin implements PluginInterface, EventSubscriberInterface +{ + /** + * Describes, for every supported virtual implementation, which packages + * provide said implementation and which extra dependencies each package + * requires to provide the implementation. + */ + private const PROVIDE_RULES = [ + 'php-http/async-client-implementation' => [ + 'symfony/http-client:>=6.3' => ['guzzlehttp/promises', 'psr/http-factory-implementation', 'php-http/httplug'], + 'symfony/http-client' => ['guzzlehttp/promises', 'php-http/message-factory', 'psr/http-factory-implementation', 'php-http/httplug'], + 'php-http/guzzle7-adapter' => [], + 'php-http/guzzle6-adapter' => [], + 'php-http/curl-client' => [], + 'php-http/react-adapter' => [], + ], + 'php-http/client-implementation' => [ + 'symfony/http-client:>=6.3' => ['psr/http-factory-implementation', 'php-http/httplug'], + 'symfony/http-client' => ['php-http/message-factory', 'psr/http-factory-implementation', 'php-http/httplug'], + 'php-http/guzzle7-adapter' => [], + 'php-http/guzzle6-adapter' => [], + 'php-http/cakephp-adapter' => [], + 'php-http/curl-client' => [], + 'php-http/react-adapter' => [], + 'php-http/buzz-adapter' => [], + 'php-http/artax-adapter' => [], + 'kriswallsmith/buzz:^1' => [], + ], + 'psr/http-client-implementation' => [ + 'symfony/http-client' => ['psr/http-factory-implementation', 'psr/http-client'], + 'guzzlehttp/guzzle' => [], + 'kriswallsmith/buzz:^1' => [], + ], + 'psr/http-message-implementation' => [ + 'php-http/discovery' => ['psr/http-factory-implementation'], + ], + 'psr/http-factory-implementation' => [ + 'nyholm/psr7' => [], + 'guzzlehttp/psr7:>=2' => [], + 'slim/psr7' => [], + 'laminas/laminas-diactoros' => [], + 'phalcon/cphalcon:^4' => [], + 'http-interop/http-factory-guzzle' => [], + 'http-interop/http-factory-diactoros' => [], + 'http-interop/http-factory-slim' => [], + 'httpsoft/http-message' => [], + ], + ]; + + /** + * Describes which package should be preferred on the left side + * depending on which one is already installed on the right side. + */ + private const STICKYNESS_RULES = [ + 'symfony/http-client' => 'symfony/framework-bundle', + 'php-http/guzzle7-adapter' => 'guzzlehttp/guzzle:^7', + 'php-http/guzzle6-adapter' => 'guzzlehttp/guzzle:^6', + 'php-http/guzzle5-adapter' => 'guzzlehttp/guzzle:^5', + 'php-http/cakephp-adapter' => 'cakephp/cakephp', + 'php-http/react-adapter' => 'react/event-loop', + 'php-http/buzz-adapter' => 'kriswallsmith/buzz:^0.15.1', + 'php-http/artax-adapter' => 'amphp/artax:^3', + 'http-interop/http-factory-guzzle' => 'guzzlehttp/psr7:^1', + 'http-interop/http-factory-slim' => 'slim/slim:^3', + ]; + + private const INTERFACE_MAP = [ + 'php-http/async-client-implementation' => [ + 'Http\Client\HttpAsyncClient', + ], + 'php-http/client-implementation' => [ + 'Http\Client\HttpClient', + ], + 'psr/http-client-implementation' => [ + 'Psr\Http\Client\ClientInterface', + ], + 'psr/http-factory-implementation' => [ + 'Psr\Http\Message\RequestFactoryInterface', + 'Psr\Http\Message\ResponseFactoryInterface', + 'Psr\Http\Message\ServerRequestFactoryInterface', + 'Psr\Http\Message\StreamFactoryInterface', + 'Psr\Http\Message\UploadedFileFactoryInterface', + 'Psr\Http\Message\UriFactoryInterface', + ], + ]; + + public static function getSubscribedEvents(): array + { + return [ + ScriptEvents::PRE_AUTOLOAD_DUMP => 'preAutoloadDump', + ScriptEvents::POST_UPDATE_CMD => 'postUpdate', + ]; + } + + public function activate(Composer $composer, IOInterface $io): void + { + } + + public function deactivate(Composer $composer, IOInterface $io) + { + } + + public function uninstall(Composer $composer, IOInterface $io) + { + } + + public function postUpdate(Event $event) + { + $composer = $event->getComposer(); + $repo = $composer->getRepositoryManager()->getLocalRepository(); + $requires = [ + $composer->getPackage()->getRequires(), + $composer->getPackage()->getDevRequires(), + ]; + $pinnedAbstractions = []; + $pinned = $composer->getPackage()->getExtra()['discovery'] ?? []; + foreach (self::INTERFACE_MAP as $abstraction => $interfaces) { + foreach (isset($pinned[$abstraction]) ? [] : $interfaces as $interface) { + if (!isset($pinned[$interface])) { + continue 2; + } + } + $pinnedAbstractions[$abstraction] = true; + } + + $missingRequires = $this->getMissingRequires($repo, $requires, 'project' === $composer->getPackage()->getType(), $pinnedAbstractions); + $missingRequires = [ + 'require' => array_fill_keys(array_merge([], ...array_values($missingRequires[0])), '*'), + 'require-dev' => array_fill_keys(array_merge([], ...array_values($missingRequires[1])), '*'), + 'remove' => array_fill_keys(array_merge([], ...array_values($missingRequires[2])), '*'), + ]; + + if (!$missingRequires = array_filter($missingRequires)) { + return; + } + + $composerJsonContents = file_get_contents(Factory::getComposerFile()); + $this->updateComposerJson($missingRequires, $composer->getConfig()->get('sort-packages')); + + $installer = null; + // Find the composer installer, hack borrowed from symfony/flex + foreach (debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT) as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Installer) { + $installer = $trace['object']; + break; + } + } + + if (!$installer) { + return; + } + + $event->stopPropagation(); + + $dispatcher = $composer->getEventDispatcher(); + $disableScripts = !method_exists($dispatcher, 'setRunScripts') || !((array) $dispatcher)["\0*\0runScripts"]; + $composer = Factory::create($event->getIO(), null, false, $disableScripts); + + /** @var Installer $installer */ + $installer = clone $installer; + if (method_exists($installer, 'setAudit')) { + $trace['object']->setAudit(false); + } + // we need a clone of the installer to preserve its configuration state but with our own service objects + $installer->__construct( + $event->getIO(), + $composer->getConfig(), + $composer->getPackage(), + $composer->getDownloadManager(), + $composer->getRepositoryManager(), + $composer->getLocker(), + $composer->getInstallationManager(), + $composer->getEventDispatcher(), + $composer->getAutoloadGenerator() + ); + if (method_exists($installer, 'setPlatformRequirementFilter')) { + $installer->setPlatformRequirementFilter(((array) $trace['object'])["\0*\0platformRequirementFilter"]); + } + + if (0 !== $installer->run()) { + file_put_contents(Factory::getComposerFile(), $composerJsonContents); + + return; + } + + $versionSelector = new VersionSelector(ClassDiscovery::safeClassExists(RepositorySet::class) ? new RepositorySet() : new Pool()); + $updateComposerJson = false; + + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) { + foreach (['require', 'require-dev'] as $key) { + if (!isset($missingRequires[$key][$package->getName()])) { + continue; + } + $updateComposerJson = true; + $missingRequires[$key][$package->getName()] = $versionSelector->findRecommendedRequireVersion($package); + } + } + + if ($updateComposerJson) { + $this->updateComposerJson($missingRequires, $composer->getConfig()->get('sort-packages')); + $this->updateComposerLock($composer, $event->getIO()); + } + } + + public function getMissingRequires(InstalledRepositoryInterface $repo, array $requires, bool $isProject, array $pinnedAbstractions): array + { + $allPackages = []; + $devPackages = method_exists($repo, 'getDevPackageNames') ? array_fill_keys($repo->getDevPackageNames(), true) : []; + + // One must require "php-http/discovery" + // to opt-in for auto-installation of virtual package implementations + if (!isset($requires[0]['php-http/discovery'])) { + $requires = [[], []]; + } + + foreach ($repo->getPackages() as $package) { + $allPackages[$package->getName()] = true; + + if (1 < \count($names = $package->getNames(false))) { + $allPackages += array_fill_keys($names, false); + + if (isset($devPackages[$package->getName()])) { + $devPackages += $names; + } + } + + if (isset($package->getRequires()['php-http/discovery'])) { + $requires[(int) isset($devPackages[$package->getName()])] += $package->getRequires(); + } + } + + $missingRequires = [[], [], []]; + $versionParser = new VersionParser(); + + if (ClassDiscovery::safeClassExists(\Phalcon\Http\Message\RequestFactory::class, false)) { + $missingRequires[0]['psr/http-factory-implementation'] = []; + $missingRequires[1]['psr/http-factory-implementation'] = []; + } + + foreach ($requires as $dev => $rules) { + $abstractions = []; + $rules = array_intersect_key(self::PROVIDE_RULES, $rules); + + while ($rules) { + $abstraction = key($rules); + + if (isset($pinnedAbstractions[$abstraction])) { + unset($rules[$abstraction]); + continue; + } + + $abstractions[] = $abstraction; + + foreach (array_shift($rules) as $candidate => $deps) { + [$candidate, $version] = explode(':', $candidate, 2) + [1 => null]; + + if (!isset($allPackages[$candidate])) { + continue; + } + if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) { + continue; + } + if ($isProject && !$dev && isset($devPackages[$candidate])) { + $missingRequires[0][$abstraction] = [$candidate]; + $missingRequires[2][$abstraction] = [$candidate]; + } else { + $missingRequires[$dev][$abstraction] = []; + } + + foreach ($deps as $dep) { + if (isset(self::PROVIDE_RULES[$dep])) { + $rules[$dep] = self::PROVIDE_RULES[$dep]; + } elseif (!isset($allPackages[$dep])) { + $missingRequires[$dev][$abstraction][] = $dep; + } elseif ($isProject && !$dev && isset($devPackages[$dep])) { + $missingRequires[0][$abstraction][] = $dep; + $missingRequires[2][$abstraction][] = $dep; + } + } + break; + } + } + + while ($abstractions) { + $abstraction = array_shift($abstractions); + + if (isset($missingRequires[$dev][$abstraction])) { + continue; + } + $candidates = self::PROVIDE_RULES[$abstraction]; + + foreach ($candidates as $candidate => $deps) { + [$candidate, $version] = explode(':', $candidate, 2) + [1 => null]; + + if (null !== $version && !$repo->findPackage($candidate, $versionParser->parseConstraints($version))) { + continue; + } + if (isset($allPackages[$candidate]) && (!$isProject || $dev || !isset($devPackages[$candidate]))) { + continue 2; + } + } + + foreach (array_intersect_key(self::STICKYNESS_RULES, $candidates) as $candidate => $stickyRule) { + [$stickyName, $stickyVersion] = explode(':', $stickyRule, 2) + [1 => null]; + if (!isset($allPackages[$stickyName]) || ($isProject && !$dev && isset($devPackages[$stickyName]))) { + continue; + } + if (null !== $stickyVersion && !$repo->findPackage($stickyName, $versionParser->parseConstraints($stickyVersion))) { + continue; + } + + $candidates = [$candidate => $candidates[$candidate]]; + break; + } + + $dep = key($candidates); + [$dep] = explode(':', $dep, 2); + $missingRequires[$dev][$abstraction] = [$dep]; + + if ($isProject && !$dev && isset($devPackages[$dep])) { + $missingRequires[2][$abstraction][] = $dep; + } + } + } + + $missingRequires[1] = array_diff_key($missingRequires[1], $missingRequires[0]); + + return $missingRequires; + } + + public function preAutoloadDump(Event $event) + { + $filesystem = new Filesystem(); + // Double realpath() on purpose, see https://bugs.php.net/72738 + $vendorDir = $filesystem->normalizePath(realpath(realpath($event->getComposer()->getConfig()->get('vendor-dir')))); + $filesystem->ensureDirectoryExists($vendorDir.'/composer'); + $pinned = $event->getComposer()->getPackage()->getExtra()['discovery'] ?? []; + $candidates = []; + + $allInterfaces = array_merge(...array_values(self::INTERFACE_MAP)); + foreach ($pinned as $abstraction => $class) { + if (isset(self::INTERFACE_MAP[$abstraction])) { + $interfaces = self::INTERFACE_MAP[$abstraction]; + } elseif (false !== $k = array_search($abstraction, $allInterfaces, true)) { + $interfaces = [$allInterfaces[$k]]; + } else { + throw new \UnexpectedValueException(sprintf('Invalid "extra.discovery" pinned in composer.json: "%s" is not one of ["%s"].', $abstraction, implode('", "', array_keys(self::INTERFACE_MAP)))); + } + + foreach ($interfaces as $interface) { + $candidates[] = sprintf("case %s: return [['class' => %s]];\n", var_export($interface, true), var_export($class, true)); + } + } + + $file = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php'; + + if (!$candidates) { + if (file_exists($file)) { + unlink($file); + } + + return; + } + + $candidates = implode(' ', $candidates); + $code = <<<EOPHP +<?php + +namespace Http\Discovery\Strategy; + +class GeneratedDiscoveryStrategy implements DiscoveryStrategy +{ + public static function getCandidates(\$type) + { + switch (\$type) { + $candidates + default: return []; + } + } +} + +EOPHP + ; + + if (!file_exists($file) || $code !== file_get_contents($file)) { + file_put_contents($file, $code); + } + + $rootPackage = $event->getComposer()->getPackage(); + $autoload = $rootPackage->getAutoload(); + $autoload['classmap'][] = $vendorDir.'/composer/GeneratedDiscoveryStrategy.php'; + $rootPackage->setAutoload($autoload); + } + + private function updateComposerJson(array $missingRequires, bool $sortPackages) + { + $file = Factory::getComposerFile(); + $contents = file_get_contents($file); + + $manipulator = new JsonManipulator($contents); + + foreach ($missingRequires as $key => $packages) { + foreach ($packages as $package => $constraint) { + if ('remove' === $key) { + $manipulator->removeSubNode('require-dev', $package); + } else { + $manipulator->addLink($key, $package, $constraint, $sortPackages); + } + } + } + + file_put_contents($file, $manipulator->getContents()); + } + + private function updateComposerLock(Composer $composer, IOInterface $io) + { + $lock = substr(Factory::getComposerFile(), 0, -4).'lock'; + $composerJson = file_get_contents(Factory::getComposerFile()); + $lockFile = new JsonFile($lock, null, $io); + $locker = ClassDiscovery::safeClassExists(RepositorySet::class) + ? new Locker($io, $lockFile, $composer->getInstallationManager(), $composerJson) + : new Locker($io, $lockFile, $composer->getRepositoryManager(), $composer->getInstallationManager(), $composerJson); + $lockData = $locker->getLockData(); + $lockData['content-hash'] = Locker::getContentHash($composerJson); + $lockFile->write($lockData); + } +} |