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);
+ }
+}