Skip to content

Commit

Permalink
Add recipe provider which loads recipes directly from the bundle.
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthias Moser committed Sep 26, 2022
1 parent 0763da1 commit de737ac
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 45 deletions.
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

0 comments on commit de737ac

Please sign in to comment.