Skip to content
Merged
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
23 changes: 8 additions & 15 deletions src/Flex.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
use Composer\IO\NullIO;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Package\BasePackage;
use Composer\Package\Locker;
use Composer\Package\Package;
use Composer\Plugin\PluginEvents;
Expand Down Expand Up @@ -77,6 +76,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
private $operations = [];
private $lock;
private $displayThanksReminder = 0;
private $ignoreUnstableReleases = false;
private $reinstall;
private static $activated = true;
private static $aliasResolveCommands = [
Expand Down Expand Up @@ -126,16 +126,8 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
Flex::$storedOperations = [];
}

$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));

$rfs = $composer->getLoop()->getHttpDownloader();

$this->downloader = $downloader = new Downloader($composer, $io, $rfs);

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

$this->configurator = new Configurator($composer, $io, $this->options);

$disable = true;
Expand Down Expand Up @@ -190,12 +182,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
}
}

if ($input->hasParameterOption('--prefer-lowest', true)) {
// When prefer-lowest is set and no stable version has been released,
// we consider "dev" more stable than "alpha", "beta" or "RC". This
// allows testing lowest versions with potential fixes applied.
BasePackage::$stabilities['dev'] = 1 + BasePackage::STABILITY_STABLE;
}
$this->ignoreUnstableReleases = $input->hasParameterOption('--prefer-lowest', true) && $input->hasParameterOption('--prefer-stable', true);

$addCommand = 'add'.(method_exists($app, 'addCommand') ? 'Command' : '');
$app->$addCommand(new Command\RecipesCommand($this, $this->lock, $rfs));
Expand All @@ -205,6 +192,12 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)

break;
}

$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));

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

/**
Expand Down
16 changes: 14 additions & 2 deletions src/PackageFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ class PackageFilter
private $symfonyConstraints;
private $downloader;
private $io;
private $ignoreUnstableReleases;

public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader)
public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader, bool $ignoreUnstableReleases = false)
{
$this->versionParser = new VersionParser();
$this->symfonyRequire = $symfonyRequire;
$this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire);
$this->symfonyConstraints = '' !== $symfonyRequire ? $this->versionParser->parseConstraints($symfonyRequire) : null;
$this->downloader = $downloader;
$this->io = $io;
$this->ignoreUnstableReleases = $ignoreUnstableReleases;
}

/**
Expand All @@ -48,6 +50,16 @@ public function __construct(IOInterface $io, string $symfonyRequire, Downloader
*/
public function removeLegacyPackages(array $data, RootPackageInterface $rootPackage, array $lockedPackages): array
{
if ($this->ignoreUnstableReleases) {
$filteredPackages = [];
foreach ($data as $package) {
if (\in_array($package->getStability(), ['stable', 'dev'], true)) {
$filteredPackages[] = $package;
}
}
$data = $filteredPackages;
}
Comment on lines +53 to +61
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is very risky and should only be done if the minimum-stability is dev. If not you will for sure break projects using flex together with --prefer-lowest/--prefer-stable. If they require anything that has only an RC/beta/alpha but dev is not allowed, it will fail resolution.

Ideally it would check per package if dev is available and if yes clean pre-release versions, if not keep everything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I very much doubt there is a use case where testing with lowest deps and neither dev nor stable are available.
I'd rather wait for a real world use case to be described before doing anything :)


if (!$this->symfonyConstraints || !$data) {
return $data;
}
Expand Down
52 changes: 52 additions & 0 deletions tests/PackageFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackage;
use Composer\Package\RootPackageInterface;
use Composer\Semver\Constraint\Constraint;
use PHPUnit\Framework\TestCase;
use Symfony\Flex\Downloader;
use Symfony\Flex\PackageFilter;

/**
Expand Down Expand Up @@ -207,4 +209,54 @@ public function provideRemoveLegacyPackages()
'symfony/bar' => ['2.8', '3.0'],
]]];
}

public function testIgnoreUnstableReleasesFiltersPreReleases()
{
$io = new NullIO();
$downloader = $this->getMockBuilder(Downloader::class)->disableOriginalConstructor()->getMock();
$filter = new PackageFilter($io, '', $downloader, true);

$stablePkg = $this->createPackageMock('pkg/stable', 'stable');
$devPkg = $this->createPackageMock('pkg/dev', 'dev');
$alphaPkg = $this->createPackageMock('pkg/alpha', 'alpha');
$betaPkg = $this->createPackageMock('pkg/beta', 'beta');
$rcPkg = $this->createPackageMock('pkg/rc', 'RC');

$root = $this->getMockBuilder(RootPackageInterface::class)->disableOriginalConstructor()->getMock();

$result = $filter->removeLegacyPackages([$stablePkg, $devPkg, $alphaPkg, $betaPkg, $rcPkg], $root, []);

$this->assertSame([$stablePkg, $devPkg], $result);
}

public function testWithoutIgnoreUnstableReleasesKeepsAll()
{
$io = new NullIO();
$downloader = $this->getMockBuilder(Downloader::class)->disableOriginalConstructor()->getMock();
$filter = new PackageFilter($io, '', $downloader, false);

$packages = [
$this->createPackageMock('pkg/stable', 'stable'),
$this->createPackageMock('pkg/dev', 'dev'),
$this->createPackageMock('pkg/alpha', 'alpha'),
$this->createPackageMock('pkg/beta', 'beta'),
$this->createPackageMock('pkg/rc', 'RC'),
];

$root = $this->getMockBuilder(RootPackageInterface::class)->disableOriginalConstructor()->getMock();

$result = $filter->removeLegacyPackages($packages, $root, []);

$this->assertSame($packages, $result);
}

private function createPackageMock(string $name, string $stability): PackageInterface
{
$package = $this->getMockBuilder(PackageInterface::class)->getMock();
$package->method('getName')->willReturn($name);
$package->method('getVersion')->willReturn('1.0.0');
$package->method('getStability')->willReturn($stability);

return $package;
}
}
Loading