Skip to content

Commit 2d3822e

Browse files
bug #1066 Fix ignoring unstable releases with --prefer-lowest and --prefer-stable (nicolas-grekas)
This PR was merged into the 2.x branch. Discussion ---------- Fix ignoring unstable releases with --prefer-lowest and --prefer-stable This is a behavior that has been broken about one year ago by [a change is composer](composer/composer@685add7). The attached implementation is way more robust since it uses a documented hook for plugins. I tried to explain why this behavior should be the default also for composer without success so far, see: composer/composer#12581 This is a must when testing lowest deps seriously, backed by years of practice :) Commits ------- 73537e7 Fix ignoring unstable releases with --prefer-lowest and --prefer-stable
2 parents b0db3b8 + 73537e7 commit 2d3822e

File tree

3 files changed

+74
-17
lines changed

3 files changed

+74
-17
lines changed

src/Flex.php

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use Composer\IO\NullIO;
3232
use Composer\Json\JsonFile;
3333
use Composer\Json\JsonManipulator;
34-
use Composer\Package\BasePackage;
3534
use Composer\Package\Locker;
3635
use Composer\Package\Package;
3736
use Composer\Plugin\PluginEvents;
@@ -77,6 +76,7 @@ class Flex implements PluginInterface, EventSubscriberInterface
7776
private $operations = [];
7877
private $lock;
7978
private $displayThanksReminder = 0;
79+
private $ignoreUnstableReleases = false;
8080
private $reinstall;
8181
private static $activated = true;
8282
private static $aliasResolveCommands = [
@@ -126,16 +126,8 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
126126
Flex::$storedOperations = [];
127127
}
128128

129-
$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));
130-
131129
$rfs = $composer->getLoop()->getHttpDownloader();
132-
133130
$this->downloader = $downloader = new Downloader($composer, $io, $rfs);
134-
135-
if ($symfonyRequire) {
136-
$this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader);
137-
}
138-
139131
$this->configurator = new Configurator($composer, $io, $this->options);
140132

141133
$disable = true;
@@ -190,12 +182,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
190182
}
191183
}
192184

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

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

206193
break;
207194
}
195+
196+
$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));
197+
198+
if ($symfonyRequire || $this->ignoreUnstableReleases) {
199+
$this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader, $this->ignoreUnstableReleases);
200+
}
208201
}
209202

210203
/**

src/PackageFilter.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ class PackageFilter
3030
private $symfonyConstraints;
3131
private $downloader;
3232
private $io;
33+
private $ignoreUnstableReleases;
3334

34-
public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader)
35+
public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader, bool $ignoreUnstableReleases = false)
3536
{
3637
$this->versionParser = new VersionParser();
3738
$this->symfonyRequire = $symfonyRequire;
38-
$this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire);
39+
$this->symfonyConstraints = '' !== $symfonyRequire ? $this->versionParser->parseConstraints($symfonyRequire) : null;
3940
$this->downloader = $downloader;
4041
$this->io = $io;
42+
$this->ignoreUnstableReleases = $ignoreUnstableReleases;
4143
}
4244

4345
/**
@@ -48,6 +50,16 @@ public function __construct(IOInterface $io, string $symfonyRequire, Downloader
4850
*/
4951
public function removeLegacyPackages(array $data, RootPackageInterface $rootPackage, array $lockedPackages): array
5052
{
53+
if ($this->ignoreUnstableReleases) {
54+
$filteredPackages = [];
55+
foreach ($data as $package) {
56+
if (\in_array($package->getStability(), ['stable', 'dev'], true)) {
57+
$filteredPackages[] = $package;
58+
}
59+
}
60+
$data = $filteredPackages;
61+
}
62+
5163
if (!$this->symfonyConstraints || !$data) {
5264
return $data;
5365
}

tests/PackageFilterTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
use Composer\Package\Loader\ArrayLoader;
1818
use Composer\Package\PackageInterface;
1919
use Composer\Package\RootPackage;
20+
use Composer\Package\RootPackageInterface;
2021
use Composer\Semver\Constraint\Constraint;
2122
use PHPUnit\Framework\TestCase;
23+
use Symfony\Flex\Downloader;
2224
use Symfony\Flex\PackageFilter;
2325

2426
/**
@@ -207,4 +209,54 @@ public function provideRemoveLegacyPackages()
207209
'symfony/bar' => ['2.8', '3.0'],
208210
]]];
209211
}
212+
213+
public function testIgnoreUnstableReleasesFiltersPreReleases()
214+
{
215+
$io = new NullIO();
216+
$downloader = $this->getMockBuilder(Downloader::class)->disableOriginalConstructor()->getMock();
217+
$filter = new PackageFilter($io, '', $downloader, true);
218+
219+
$stablePkg = $this->createPackageMock('pkg/stable', 'stable');
220+
$devPkg = $this->createPackageMock('pkg/dev', 'dev');
221+
$alphaPkg = $this->createPackageMock('pkg/alpha', 'alpha');
222+
$betaPkg = $this->createPackageMock('pkg/beta', 'beta');
223+
$rcPkg = $this->createPackageMock('pkg/rc', 'RC');
224+
225+
$root = $this->getMockBuilder(RootPackageInterface::class)->disableOriginalConstructor()->getMock();
226+
227+
$result = $filter->removeLegacyPackages([$stablePkg, $devPkg, $alphaPkg, $betaPkg, $rcPkg], $root, []);
228+
229+
$this->assertSame([$stablePkg, $devPkg], $result);
230+
}
231+
232+
public function testWithoutIgnoreUnstableReleasesKeepsAll()
233+
{
234+
$io = new NullIO();
235+
$downloader = $this->getMockBuilder(Downloader::class)->disableOriginalConstructor()->getMock();
236+
$filter = new PackageFilter($io, '', $downloader, false);
237+
238+
$packages = [
239+
$this->createPackageMock('pkg/stable', 'stable'),
240+
$this->createPackageMock('pkg/dev', 'dev'),
241+
$this->createPackageMock('pkg/alpha', 'alpha'),
242+
$this->createPackageMock('pkg/beta', 'beta'),
243+
$this->createPackageMock('pkg/rc', 'RC'),
244+
];
245+
246+
$root = $this->getMockBuilder(RootPackageInterface::class)->disableOriginalConstructor()->getMock();
247+
248+
$result = $filter->removeLegacyPackages($packages, $root, []);
249+
250+
$this->assertSame($packages, $result);
251+
}
252+
253+
private function createPackageMock(string $name, string $stability): PackageInterface
254+
{
255+
$package = $this->getMockBuilder(PackageInterface::class)->getMock();
256+
$package->method('getName')->willReturn($name);
257+
$package->method('getVersion')->willReturn('1.0.0');
258+
$package->method('getStability')->willReturn($stability);
259+
260+
return $package;
261+
}
210262
}

0 commit comments

Comments
 (0)