diff --git a/src/Flex.php b/src/Flex.php index 6c53270a..5461c3eb 100644 --- a/src/Flex.php +++ b/src/Flex.php @@ -39,6 +39,7 @@ use Composer\Plugin\PrePoolCreateEvent; use Composer\Script\Event; use Composer\Script\ScriptEvents; +use Composer\Semver\Constraint\Constraint; use Composer\Semver\VersionParser; use Symfony\Component\Console\Exception\ExceptionInterface as ConsoleExceptionInterface; use Symfony\Component\Console\Input\ArgvInput; @@ -219,7 +220,15 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__) break; } - $symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? '')); + $rawSymfonyRequire = getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''); + $symfonyRequire = preg_replace('/\.x$/', '.x-dev', $rawSymfonyRequire); + + if ($rawSymfonyRequire) { + $parsedConstraint = (new VersionParser())->parseConstraints($rawSymfonyRequire); + if ($parsedConstraint instanceof Constraint && '==' === $parsedConstraint->getOperator()) { + $io->writeError(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint. Did you mean "%s.*" or "^%s"?', $rawSymfonyRequire, $rawSymfonyRequire, $rawSymfonyRequire)); + } + } if ($symfonyRequire || $this->ignorePreleases) { $this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader, $this->ignorePreleases); diff --git a/tests/FlexTest.php b/tests/FlexTest.php index ef82da5b..f4c51741 100644 --- a/tests/FlexTest.php +++ b/tests/FlexTest.php @@ -111,6 +111,47 @@ public function testPostInstall() ); } + #[DataProvider('getSymfonyRequireConstraints')] + public function testSymfonyRequireExactVersionWarning(string $constraint, bool $expectWarning) + { + $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE); + + $package = $this->mockRootPackage(); + $package->method('getRequires')->willReturn([new Link('dummy', 'symfony/flex', class_exists(MatchAllConstraint::class) ? new MatchAllConstraint() : null)]); + + $composer = $this->mockComposer($this->mockLocker(), $package, Factory::createConfig($io)); + if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) { + $composer->setRepositoryManager($this->mockManager()); + } + + putenv('SYMFONY_REQUIRE='.$constraint); + try { + (new Flex())->activate($composer, $io); + } finally { + putenv('SYMFONY_REQUIRE'); + } + + if ($expectWarning) { + $this->assertStringContainsString(\sprintf('SYMFONY_REQUIRE="%s" is an exact version constraint', $constraint), $io->getOutput()); + } else { + $this->assertStringNotContainsString('is an exact version constraint', $io->getOutput()); + } + } + + public static function getSymfonyRequireConstraints(): array + { + return [ + 'exact major' => ['7', true], + 'exact minor' => ['7.4', true], + 'exact patch' => ['7.4.1', true], + 'caret' => ['^7.4', false], + 'tilde' => ['~7.4', false], + 'wildcard' => ['7.4.*', false], + 'greater or equal' => ['>=7.4', false], + 'x-range' => ['7.4.x', false], + ]; + } + public function testActivateLoadsClasses() { $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE);