8000 Add recipe provider which loads recipes directly from the bundle. by ersatzhero · Pull Request #952 · symfony/flex · GitHub
[go: up one dir, main page]

Skip to content

Add recipe provider which loads recipes directly from the bundle. #952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Command/UpdateRecipesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,29 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Flex\Configurator;
use Symfony\Flex\Downloader;
use Symfony\Flex\Flex;
use Symfony\Flex\GithubApi;
use Symfony\Flex\InformationOperation;
use Symfony\Flex\Lock;
use Symfony\Flex\Recipe;
use Symfony\Flex\RecipeProviderInterface;
use Symfony\Flex\Update\RecipePatcher;
use Symfony\Flex\Update\RecipeUpdate;

class UpdateRecipesCommand extends BaseCommand
{
/** @var Flex */
private $flex;
private $downloader;
private RecipeProviderInterface $recipeProvider;
private $configurator;
private $rootDir;
private $githubApi;
private $processExecutor;

public function __construct(/* cannot be type-hinted */ $flex, Downloader $downloader, $httpDownloader, Configurator $configurator, string $rootDir)
public function __construct(/* cannot be type-hinted */ $flex, RecipeProviderInterface $recipeProvider, $httpDownloader, Configurator $configurator, string $rootDir)
{
$this->flex = $flex;
$this->downloader = $downloader;
$this->recipeProvider = $recipeProvider;
$this->configurator = $configurator;
$this->rootDir = $rootDir;
$this->githubApi = new GithubApi($httpDownloader);
Expand Down Expand Up @@ -268,7 +268,7 @@ private function getRecipe(PackageInterface $package, string $recipeRef = null,
if (null !== $recipeRef) {
$operation->setSpecificRecipeVersion($recipeRef, $recipeVersion);
}
$recipes = $this->downloader->getRecipes([$operation]);
$recipes = $this->recipeProvider->getRecipes([$operation]);

if (0 === \count($recipes['manifests'] ?? [])) {
return null;
Expand Down
112 changes: 112 additions & 0 deletions src/CompositeRecipeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

namespace Symfony\Flex;

class CompositeRecipeProvider implements RecipeProviderInterface
{
/**
* @var RecipeProviderInterface[]
*/
private array $recipeProviders;

/**
* @param RecipeProviderInterface[] $recipeProviders
*
* @throws \InvalidArgumentException
*/
public function __construct(array $recipeProviders)
{
$this->recipeProviders = array_reduce(
$recipeProviders,
function (array $providers, RecipeProviderInterface $provider) {
if (self::class == $provider::class) {
throw new \InvalidArgumentException('You cannot add an instance of this provider to itself.');
}
$providers[$provider::class] = $provider;

return $providers;
},
[]);
}

/**
* This method adds an instance RecipeProviderInterface to this provider.
* You can only have one instance per class registered in this provider.
*
* @return $this
*
* @throws \InvalidArgumentException
*/
public function add(RecipeProviderInterface $recipeProvider): self
{
if (self::class == $recipeProvider::class) {
throw new \InvalidArgumentException('You cannot add an instance of this provider to itself.');
}
if (isset($this->recipeProviders[$recipeProvider::class])) {
throw new \InvalidArgumentException('Given Provider has been added already.');
}
$this->recipeProviders[] = $recipeProvider;

return $this;
}

/**
* {@inheritDoc}
*/
public function isEnabled(): bool
{
return array_reduce($this->recipeProviders, function (bool $isEnabled, RecipeProviderInterface $provider) { return $provider->isEnabled() && $isEnabled; }, true);
}

/**
* {@inheritDoc}
*/
public function disable(): void
{
array_walk($this->recipeProviders, function (RecipeProviderInterface $provider) { $provider->disable(); });
}

/**
* {@inheritDoc}
*/
public function getVersions(): array
{
return array_reduce($this->recipeProviders, function (array $carry, RecipeProviderInterface $provider) { return array_merge($carry, $provider->getVersions()); }, []);
}

/**
* {@inheritDoc}
*/
public function getAliases(): array
{
return array_reduce($this->recipeProviders, function (array $carry, RecipeProviderInterface $provider) { return array_merge($carry, $provider->getAliases()); }, []);
}

/**
* {@inheritDoc}
*/
public function getRecipes(array $operations): array
{
return array_reduce($this->recipeProviders, function (array $carry, RecipeProviderInterface $provider) use ($operations) { return array_merge_recursive($carry, $provider->getRecipes($operations)); }, []);
}

/**
* {@inheritDoc}
*/
public function removeRecipeFromIndex(string $packageName, string $version): void
{
array_walk($this->recipeProviders, function (RecipeProviderInterface $provider) use ($packageName, $version) { $provider->removeRecipeFromIndex($packageName, $version); });
}

public function getSessionId(): string
{
return implode(' ', array_reduce(
$this->recipeProviders,
function (array $carry, RecipeProviderInterface $provider) {
$carry[] = $provider::class.'=>'.$provider->getSessionId();

return $carry;
},
[]));
}
}
36 changes: 23 additions & 13 deletions src/Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

use Composer\Cache;
use Composer\Composer;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\IO\IOInterface;
Expand All @@ -26,7 +25,7 @@
* @author Fabien Potencier <fabien@symfony.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class Downloader
class Downloader implements RecipeProviderInterface
{
private const DEFAULT_ENDPOINTS = [
'https://raw.githubusercontent.com/symfony/recipes/flex/main/index.json',
Expand Down Expand Up @@ -95,39 +94,52 @@ public function __construct(Composer $composer, IoInterface $io, HttpDownloader
$this->composer = $composer;
}

/**
* {@inheritDoc}
*/
public function getSessionId(): string
{
return $this->sess;
}

public function isEnabled()
/**
* {@inheritDoc}
*/
public function isEnabled(): bool
{
return $this->enabled;
}

public function disable()
/**
* {@inheritDoc}
*/
public function disable(): void
{
$this->enabled = false;
}

public function getVersions()
/**
* {@inheritDoc}
*/
public function getVersions(): array
{
$this->initialize();

return self::$versions ?? self::$versions = current($this->get([$this->legacyEndpoint.'/versions.json']));
}

public function getAliases()
/**
* {@inheritDoc}
*/
public function getAliases(): array
{
$this->initialize();

return self::$aliases ?? self::$aliases = current($this->get([$this->legacyEndpoint.'/aliases.json']));
}

/**
* Downloads recipes.
*
* @param OperationInterface[] $operations
* {@inheritDoc}
*/
public function getRecipes(array $operations): array
{
Expand Down Expand Up @@ -307,11 +319,9 @@ public function getRecipes(array $operations): array
}

/**
* Used to "hide" a recipe version so that the next most-recent will be returned.
*
* This is used when resolving "conflicts".
* {@inheritDoc}
*/
public function removeRecipeFromIndex(string $packageName, string $version)
public function removeRecipeFromIndex(string $packageName, string $version): void
{
unset($this->index[$packageName][$version]);
}
Expand Down
36 changes: 20 additions & 16 deletions src/Flex.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
private $config;
private $options;
private $configurator;
private $downloader;
private RecipeProviderInterface $recipeProvider;

/**
* @var Installer
Expand Down Expand Up @@ -112,10 +112,14 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)

$rfs = Factory::createHttpDownloader($this->io, $this->config);

$this->downloader = $downloader = new Downloader($composer, $io, $rfs);
$this->recipeProvider = new CompositeRecipeProvider(
[
new Downloader($composer, $io, $rfs),
new LocalRecipeProvider($composer),
]);

if ($symfonyRequire) {
$this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader);
$this->filter = new PackageFilter($io, $symfonyRequire, $this->recipeProvider);
}

$composerFile = Factory::getComposerFile();
Expand All @@ -134,7 +138,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
}
}
if ($disable) {
$downloader->disable();
$this->recipeProvider->disable();
}

$backtrace = $this->configureInstaller();
Expand All @@ -153,7 +157,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
$input = $trace['args'][0];
$app = $trace['object'];

$resolver = new PackageResolver($this->downloader);
$resolver = new PackageResolver($this->recipeProvider);

try {
$command = $input->getFirstArgument();
Expand Down Expand Up @@ -186,7 +190,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)

$app->add(new Command\RecipesCommand($this, $this->lock, $rfs));
$app->add(new Command\InstallRecipesCommand($this, $this->options->get('root-dir'), $this->options->get('runtime')['dotenv_path'] ?? '.env'));
$app->add(new Command\UpdateRecipesCommand($this, $this->downloader, $rfs, $this->configurator, $this->options->get('root-dir')));
$app->add(new Command\UpdateRecipesCommand($this, $this->recipeProvider, $rfs, $this->configurator, $this->options->get('root-dir')));
$app->add(new Command\DumpEnvCommand($this->config, $this->options));

break;
Expand Down Expand Up @@ -215,7 +219,7 @@ public function configureInstaller()
}

if (isset($trace['object']) && $trace['object'] instanceof GlobalCommand) {
$this->downloader->disable();
$this->recipeProvider->disable();
}
}

Expand All @@ -224,7 +228,7 @@ public function configureInstaller()

public function configureProject(Event $event)
{
if (!$this->downloader->isEnabled()) {
if (!$this->recipeProvider->isEnabled()) {
$this->io->writeError('<warning>Project configuration is disabled: "symfony/flex" not found in the root composer.json</>');

return;
Expand Down Expand Up @@ -312,7 +316,7 @@ public function update(Event $event, $operations = [])
$manipulator = new JsonManipulator($contents);
$sortPackages = $this->composer->getConfig()->get('sort-packages');
$symfonyVersion = $json['extra']['symfony']['require'] ?? null;
$versions = $symfonyVersion ? $this->downloader->getVersions() : null;
$versions = $symfonyVersion ? $this->recipeProvider->getVersions() : null;
foreach (['require', 'require-dev'] as $type) {
if (!isset($json['flex-'.$type])) {
continue;
Expand Down Expand Up @@ -363,15 +367,15 @@ public function install(Event $event)
$this->finish($rootDir);
}

if ($this->downloader->isEnabled()) {
if ($this->recipeProvider->isEnabled()) {
$this->io->writeError('Run <comment>composer recipes</> at any time to see the status of your Symfony recipes.');
$this->io->writeError('');
}

return;
}

$this->io->writeError(sprintf('<info>Symfony operations: %d recipe%s (%s)</>', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->downloader->getSessionId()));
$this->io->writeError(sprintf('<info>Symfony operations: %d recipe%s (%s)</>', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->recipeProvider->getSessionId()));
$installContribs = $this->composer->getPackage()->getExtra()['symfony']['allow-contrib'] ?? false;
$manifest = null;
$originalComposerJsonHash = $this->getComposerJsonHash();
Expand Down Expand Up @@ -527,13 +531,13 @@ public function executeAutoScripts(Event $event)
*/
public function fetchRecipes(array $operations, bool $reset): array
{
if (!$this->downloader->isEnabled()) {
if (!$this->recipeProvider->isEnabled()) {
$this->io->writeError('<warning>Symfony recipes are disabled: "symfony/flex" not found in the root composer.json</>');

return [];
}
$devPackages = null;
$data = $this->downloader->getRecipes($operations);
$data = $this->recipeProvider->getRecipes($operations);
$manifests = $data['manifests'] ?? [];
$locks = $data['locks'] ?? [];
// symfony/flex recipes should always be applied first
Expand Down Expand Up @@ -562,8 +566,8 @@ public function fetchRecipes(array $operations, bool $reset): array
}

while ($this->doesRecipeConflict($manifests[$name] ?? [], $operation)) {
$this->downloader->removeRecipeFromIndex($name, $manifests[$name]['version']);
$newData = $this->downloader->getRecipes([$operation]);
$this->recipeProvider->removeRecipeFromIndex($name, $manifests[$name]['version']);
$newData = $this->recipeProvider->getRecipes([$operation]);
$newManifests = $newData['manifests'] ?? [];

if (!isset($newManifests[$name])) {
Expand Down Expand Up @@ -751,7 +755,7 @@ private function unpack(Event $event)
}
}

$unpacker = new Unpacker($this->composer, new PackageResolver($this->downloader), $this->dryRun);
$unpacker = new Unpacker($this->composer, new PackageResolver($this->recipeProvider), $this->dryRun);
$result = $unpacker->unpack($unpackOp);

if (!$result->getUnpacked()) {
Expand Down
Loading
0