diff --git a/src/Command/RemoveCommand.php b/src/Command/RemoveCommand.php deleted file mode 100644 index d6cd698ba..000000000 --- a/src/Command/RemoveCommand.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Flex\Command; - -use Composer\Command\RemoveCommand as BaseRemoveCommand; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Flex\PackageResolver; - -class RemoveCommand extends BaseRemoveCommand -{ - private $resolver; - - public function __construct(PackageResolver $resolver) - { - $this->resolver = $resolver; - - parent::__construct(); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages'))); - - return parent::execute($input, $output); - } -} diff --git a/src/Command/RequireCommand.php b/src/Command/RequireCommand.php deleted file mode 100644 index faa76c5b4..000000000 --- a/src/Command/RequireCommand.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Flex\Command; - -use Composer\Command\RequireCommand as BaseRequireCommand; -use Composer\Factory; -use Composer\Json\JsonFile; -use Composer\Json\JsonManipulator; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Flex\PackageResolver; - -class RequireCommand extends BaseRequireCommand -{ - private $resolver; - private $updateComposerLock; - - public function __construct(PackageResolver $resolver, \Closure $updateComposerLock = null) - { - $this->resolver = $resolver; - $this->updateComposerLock = $updateComposerLock; - - parent::__construct(); - } - - protected function configure() - { - parent::configure(); - $this->addOption('no-unpack', null, InputOption::VALUE_NONE, '[DEPRECATED] Disable unpacking Symfony packs in composer.json.'); - $this->addOption('unpack', null, InputOption::VALUE_NONE, '[DEPRECATED] Unpacking is now enabled by default.'); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - if ($input->getOption('no-unpack')) { - $this->getIO()->writeError('The "--unpack" command line option is deprecated; unpacking is now enabled by default.'); - } - - if ($input->getOption('unpack')) { - $this->getIO()->writeError('The "--unpack" command line option is deprecated; unpacking is now enabled by default.'); - } - - $packages = $this->resolver->resolve($input->getArgument('packages'), true); - if ($packages) { - $input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages'), true)); - } - - $file = Factory::getComposerFile(); - $contents = file_get_contents($file); - $json = JsonFile::parseJson($contents); - - if (\array_key_exists('require-dev', $json) && !$json['require-dev'] && (new JsonManipulator($contents))->removeMainKey('require-dev')) { - $manipulator = new JsonManipulator($contents); - $manipulator->addLink('require-dev', 'php', '*'); - file_put_contents($file, $manipulator->getContents()); - } else { - $file = null; - } - unset($contents, $json, $manipulator); - - try { - return parent::execute($input, $output) ?? 0; - } finally { - if (null !== $file) { - $manipulator = new JsonManipulator(file_get_contents($file)); - $manipulator->removeSubNode('require-dev', 'php'); - file_put_contents($file, $manipulator->getContents()); - - if ($this->updateComposerLock) { - ($this->updateComposerLock)(); - } - } - } - } -} diff --git a/src/Command/UpdateCommand.php b/src/Command/UpdateCommand.php deleted file mode 100644 index 364b772eb..000000000 --- a/src/Command/UpdateCommand.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Flex\Command; - -use Composer\Command\UpdateCommand as BaseUpdateCommand; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Flex\PackageResolver; - -class UpdateCommand extends BaseUpdateCommand -{ - private $resolver; - - public function __construct(PackageResolver $resolver) - { - $this->resolver = $resolver; - - parent::__construct(); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages'))); - - return parent::execute($input, $output); - } -} diff --git a/src/Flex.php b/src/Flex.php index 0ae31c32b..f20808c0d 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -172,7 +172,6 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) } if (isset(self::$aliasResolveCommands[$command])) { - // early resolve for BC with Composer 1.0 if ($input->hasArgument('packages')) { $input->setArgument('packages', $resolver->resolve($input->getArgument('packages'), self::$aliasResolveCommands[$command])); } @@ -185,9 +184,6 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) BasePackage::$stabilities['dev'] = 1 + BasePackage::STABILITY_STABLE; } - $app->add(new Command\RequireCommand($resolver, \Closure::fromCallable([$this, 'updateComposerLock']))); - $app->add(new Command\UpdateCommand($resolver)); - $app->add(new Command\RemoveCommand($resolver)); $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'))); @@ -208,6 +204,14 @@ public function configureInstaller() foreach ($backtrace as $trace) { if (isset($trace['object']) && $trace['object'] instanceof Installer) { $this->installer = $trace['object']->setSuggestedPackagesReporter(new SuggestedPackagesReporter(new NullIO())); + + $updateAllowList = \Closure::bind(function () { + return $this->updateWhitelist ?? $this->updateAllowList; + }, $this->installer, $this->installer)(); + + if (['php' => 0] === $updateAllowList) { + $this->dryRun = true; // prevent recipes from being uninstalled when removing a pack + } } if (isset($trace['object']) && $trace['object'] instanceof GlobalCommand) { @@ -313,6 +317,9 @@ public function update(Event $event, $operations = []) if (!isset($json['flex-'.$type])) { continue; } + + $this->io->writeError(sprintf('Using section "flex-%s" in composer.json is deprecated, use "%1$s" instead.', $type)); + foreach ($json['flex-'.$type] as $package => $constraint) { if ($symfonyVersion && '*' === $constraint && isset($versions['splits'][$package])) { // replace unbounded constraints for symfony/* packages by extra.symfony.require @@ -538,6 +545,7 @@ public function fetchRecipes(array $operations, bool $reset): array $recipes = [ 'symfony/framework-bundle' => null, ]; + $packRecipes = []; $metaRecipes = []; foreach ($operations as $operation) { @@ -587,12 +595,16 @@ public function fetchRecipes(array $operations, bool $reset): array } if (isset($manifests[$name])) { - if ('metapackage' === $package->getType()) { - $metaRecipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []); + $recipe = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []); + + if ('symfony-pack' === $package->getType()) { + $packRecipes[$name] = $recipe; + } elseif ('metapackage' === $package->getType()) { + $metaRecipes[$name] = $recipe; } elseif ('symfony/flex' === $name) { - $flexRecipe = [$name => new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? [])]; + $flexRecipe = [$name => $recipe]; } else { - $recipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []); + $recipes[$name] = $recipe; } } @@ -618,7 +630,7 @@ public function fetchRecipes(array $operations, bool $reset): array } } - return array_merge($flexRecipe, $metaRecipes, array_filter($recipes)); + return array_merge($flexRecipe, $packRecipes, $metaRecipes, array_filter($recipes)); } public function truncatePackages(PrePoolCreateEvent $event) diff --git a/src/ScriptExecutor.php b/src/ScriptExecutor.php index 69842b3f0..c9fbd999c 100644 --- a/src/ScriptExecutor.php +++ b/src/ScriptExecutor.php @@ -127,6 +127,10 @@ private function expandPhpScript(string $cmd): string $arguments[] = '--php-ini='.$ini; } + if ($memoryLimit = (string) getenv('COMPOSER_MEMORY_LIMIT')) { + $arguments[] = "-d memory_limit={$memoryLimit}"; + } + $phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments)); return ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$cmd; diff --git a/src/SymfonyBundle.php b/src/SymfonyBundle.php index 5c1cd1a62..7d1d8a1c3 100644 --- a/src/SymfonyBundle.php +++ b/src/SymfonyBundle.php @@ -107,6 +107,9 @@ private function isBundleClass(string $class, string $path, bool $isPsr4): bool } // heuristic that should work in almost all cases - return false !== strpos(file_get_contents($classPath), 'Symfony\Component\HttpKernel\Bundle\Bundle'); + $classContents = file_get_contents($classPath); + + return (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle')) + || (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle')); } } diff --git a/tests/FlexTest.php b/tests/FlexTest.php index 26ef496c1..ab8fe4af4 100644 --- a/tests/FlexTest.php +++ b/tests/FlexTest.php @@ -187,6 +187,7 @@ public function testFetchRecipesOrder() ['name' => 'symfony/flex', 'type' => 'composer-plugin'], ['name' => 'symfony/framework-bundle', 'type' => 'library'], ['name' => 'symfony/webapp-meta', 'type' => 'metapackage'], + ['name' => 'symfony/webapp-pack', 'type' => 'symfony-pack'], ]; $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE); @@ -209,6 +210,7 @@ public function testFetchRecipesOrder() $this->assertSame([ 'symfony/flex', + 'symfony/webapp-pack', 'symfony/webapp-meta', 'symfony/framework-bundle', 'symfony/console', diff --git a/tests/ScriptExecutorTest.php b/tests/ScriptExecutorTest.php new file mode 100644 index 000000000..211910ac4 --- /dev/null +++ b/tests/ScriptExecutorTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Tests; + +use Composer\Composer; +use Composer\IO\NullIO; +use Composer\Util\ProcessExecutor; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Flex\Options; +use Symfony\Flex\ScriptExecutor; + +final class ScriptExecutorTest extends TestCase +{ + /** + * @backupGlobals enabled + */ + public function testMemoryLimit(): void + { + $command = './command.php'; + $memoryLimit = '32M'; + putenv("COMPOSER_MEMORY_LIMIT={$memoryLimit}"); + $executorMock = $this->createMock(ProcessExecutor::class); + $scriptExecutor = new ScriptExecutor(new Composer(), new NullIO(), new Options(), $executorMock); + + $phpFinder = new PhpExecutableFinder(); + if (!$php = $phpFinder->find(false)) { + throw new \RuntimeException('The PHP executable could not be found, add it to your PATH and try again.'); + } + + $arguments = $phpFinder->findArguments(); + $ini = php_ini_loaded_file(); + $arguments[] = "--php-ini={$ini}"; + $arguments[] = "-d memory_limit={$memoryLimit}"; + + $phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments)); + + $expectedCommand = ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$command; + + $executorMock + ->method('execute') + ->with($expectedCommand) + ->willReturn(0) + ; + $this->expectNotToPerformAssertions(); + + $scriptExecutor->execute('php-script', $command); + } +}