Make VersionSelector prefer stable by default and allow specifying a PHP version that must be matched, fixes #4318
parent
4f934d9282
commit
507415e404
|
@ -321,7 +321,7 @@ EOT
|
||||||
return $this->repos;
|
return $this->repos;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
|
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null)
|
||||||
{
|
{
|
||||||
if ($requires) {
|
if ($requires) {
|
||||||
$requires = $this->normalizeRequirements($requires);
|
$requires = $this->normalizeRequirements($requires);
|
||||||
|
@ -331,7 +331,7 @@ EOT
|
||||||
foreach ($requires as $requirement) {
|
foreach ($requires as $requirement) {
|
||||||
if (!isset($requirement['version'])) {
|
if (!isset($requirement['version'])) {
|
||||||
// determine the best version automatically
|
// determine the best version automatically
|
||||||
$version = $this->findBestVersionForPackage($input, $requirement['name']);
|
$version = $this->findBestVersionForPackage($input, $requirement['name'], $phpVersion);
|
||||||
$requirement['version'] = $version;
|
$requirement['version'] = $version;
|
||||||
|
|
||||||
$io->writeError(sprintf(
|
$io->writeError(sprintf(
|
||||||
|
@ -426,7 +426,7 @@ EOT
|
||||||
);
|
);
|
||||||
|
|
||||||
if (false === $constraint) {
|
if (false === $constraint) {
|
||||||
$constraint = $this->findBestVersionForPackage($input, $package);
|
$constraint = $this->findBestVersionForPackage($input, $package, $phpVersion);
|
||||||
|
|
||||||
$io->writeError(sprintf(
|
$io->writeError(sprintf(
|
||||||
'Using version <info>%s</info> for <info>%s</info>',
|
'Using version <info>%s</info> for <info>%s</info>',
|
||||||
|
@ -591,14 +591,15 @@ EOT
|
||||||
*
|
*
|
||||||
* @param InputInterface $input
|
* @param InputInterface $input
|
||||||
* @param string $name
|
* @param string $name
|
||||||
|
* @param string $phpVersion
|
||||||
* @throws \InvalidArgumentException
|
* @throws \InvalidArgumentException
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function findBestVersionForPackage(InputInterface $input, $name)
|
private function findBestVersionForPackage(InputInterface $input, $name, $phpVersion)
|
||||||
{
|
{
|
||||||
// find the latest version allowed in this pool
|
// find the latest version allowed in this pool
|
||||||
$versionSelector = new VersionSelector($this->getPool($input));
|
$versionSelector = new VersionSelector($this->getPool($input));
|
||||||
$package = $versionSelector->findBestCandidate($name);
|
$package = $versionSelector->findBestCandidate($name, null, $phpVersion);
|
||||||
|
|
||||||
if (!$package) {
|
if (!$package) {
|
||||||
throw new \InvalidArgumentException(sprintf(
|
throw new \InvalidArgumentException(sprintf(
|
||||||
|
|
|
@ -96,12 +96,18 @@ EOT
|
||||||
$composer = $this->getComposer();
|
$composer = $this->getComposer();
|
||||||
$repos = $composer->getRepositoryManager()->getRepositories();
|
$repos = $composer->getRepositoryManager()->getRepositories();
|
||||||
|
|
||||||
|
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
||||||
|
// initialize $this->repos as it is used by the parent InitCommand
|
||||||
$this->repos = new CompositeRepository(array_merge(
|
$this->repos = new CompositeRepository(array_merge(
|
||||||
array(new PlatformRepository),
|
array(new PlatformRepository(array(), $platformOverrides)),
|
||||||
$repos
|
$repos
|
||||||
));
|
));
|
||||||
|
|
||||||
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
|
$phpPackages = $this->repos->findPackages('php');
|
||||||
|
$phpPackage = reset($phpPackages);
|
||||||
|
$phpVersion = $phpPackage->getVersion();
|
||||||
|
unset($phpPackage, $phpPackages);
|
||||||
|
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion);
|
||||||
|
|
||||||
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
|
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
|
||||||
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
|
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
|
||||||
|
|
|
@ -223,6 +223,11 @@ abstract class BasePackage implements PackageInterface
|
||||||
return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
|
return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStabilityPriority()
|
||||||
|
{
|
||||||
|
return self::$stabilities[$this->getStability()];
|
||||||
|
}
|
||||||
|
|
||||||
public function __clone()
|
public function __clone()
|
||||||
{
|
{
|
||||||
$this->repository = null;
|
$this->repository = null;
|
||||||
|
|
|
@ -17,6 +17,8 @@ use Composer\Package\PackageInterface;
|
||||||
use Composer\Package\Loader\ArrayLoader;
|
use Composer\Package\Loader\ArrayLoader;
|
||||||
use Composer\Package\Dumper\ArrayDumper;
|
use Composer\Package\Dumper\ArrayDumper;
|
||||||
use Composer\Semver\VersionParser as SemverVersionParser;
|
use Composer\Semver\VersionParser as SemverVersionParser;
|
||||||
|
use Composer\Semver\Semver;
|
||||||
|
use Composer\Semver\Constraint\Constraint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the best possible version for a package
|
* Selects the best possible version for a package
|
||||||
|
@ -41,13 +43,22 @@ class VersionSelector
|
||||||
*
|
*
|
||||||
* @param string $packageName
|
* @param string $packageName
|
||||||
* @param string $targetPackageVersion
|
* @param string $targetPackageVersion
|
||||||
|
* @param string $targetPhpVersion
|
||||||
* @return PackageInterface|bool
|
* @return PackageInterface|bool
|
||||||
*/
|
*/
|
||||||
public function findBestCandidate($packageName, $targetPackageVersion = null)
|
public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferStable = true)
|
||||||
{
|
{
|
||||||
$constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
|
$constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
|
||||||
$candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true);
|
$candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true);
|
||||||
|
|
||||||
|
if ($targetPhpVersion) {
|
||||||
|
$phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion));
|
||||||
|
$candidates = array_filter($candidates, function ($pkg) use ($phpConstraint) {
|
||||||
|
$reqs = $pkg->getRequires();
|
||||||
|
return !isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!$candidates) {
|
if (!$candidates) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +66,9 @@ class VersionSelector
|
||||||
// select highest version if we have many
|
// select highest version if we have many
|
||||||
$package = reset($candidates);
|
$package = reset($candidates);
|
||||||
foreach ($candidates as $candidate) {
|
foreach ($candidates as $candidate) {
|
||||||
|
if ($preferStable && $package->getStabilityPriority() < $candidate->getStabilityPriority()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
|
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
|
||||||
$package = $candidate;
|
$package = $candidate;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
namespace Composer\Test\Package\Version;
|
namespace Composer\Test\Package\Version;
|
||||||
|
|
||||||
use Composer\Package\Version\VersionSelector;
|
use Composer\Package\Version\VersionSelector;
|
||||||
|
use Composer\Package\Package;
|
||||||
|
use Composer\Package\Link;
|
||||||
use Composer\Semver\VersionParser;
|
use Composer\Semver\VersionParser;
|
||||||
|
|
||||||
class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
||||||
|
@ -25,9 +27,9 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
$packageName = 'foobar';
|
$packageName = 'foobar';
|
||||||
|
|
||||||
$package1 = $this->createMockPackage('1.2.1');
|
$package1 = $this->createPackage('1.2.1');
|
||||||
$package2 = $this->createMockPackage('1.2.2');
|
$package2 = $this->createPackage('1.2.2');
|
||||||
$package3 = $this->createMockPackage('1.2.0');
|
$package3 = $this->createPackage('1.2.0');
|
||||||
$packages = array($package1, $package2, $package3);
|
$packages = array($package1, $package2, $package3);
|
||||||
|
|
||||||
$pool = $this->createMockPool();
|
$pool = $this->createMockPool();
|
||||||
|
@ -40,7 +42,70 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
||||||
$best = $versionSelector->findBestCandidate($packageName);
|
$best = $versionSelector->findBestCandidate($packageName);
|
||||||
|
|
||||||
// 1.2.2 should be returned because it's the latest of the returned versions
|
// 1.2.2 should be returned because it's the latest of the returned versions
|
||||||
$this->assertEquals($package2, $best, 'Latest version should be 1.2.2');
|
$this->assertSame($package2, $best, 'Latest version should be 1.2.2');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLatestVersionIsReturnedThatMatchesPhpRequirement()
|
||||||
|
{
|
||||||
|
$packageName = 'foobar';
|
||||||
|
|
||||||
|
$parser = new VersionParser;
|
||||||
|
$package1 = $this->createPackage('1.0.0');
|
||||||
|
$package2 = $this->createPackage('2.0.0');
|
||||||
|
$package1->setRequires(array('php' => new Link($packageName, 'php', $parser->parseConstraints('>=5.4'), 'requires', '>=5.4')));
|
||||||
|
$package2->setRequires(array('php' => new Link($packageName, 'php', $parser->parseConstraints('>=5.6'), 'requires', '>=5.6')));
|
||||||
|
$packages = array($package1, $package2);
|
||||||
|
|
||||||
|
$pool = $this->createMockPool();
|
||||||
|
$pool->expects($this->once())
|
||||||
|
->method('whatProvides')
|
||||||
|
->with($packageName, null, true)
|
||||||
|
->will($this->returnValue($packages));
|
||||||
|
|
||||||
|
$versionSelector = new VersionSelector($pool);
|
||||||
|
$best = $versionSelector->findBestCandidate($packageName, null, '5.5.0');
|
||||||
|
|
||||||
|
$this->assertSame($package1, $best, 'Latest version supporting php 5.5 should be returned (1.0.0)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMostStableVersionIsReturned()
|
||||||
|
{
|
||||||
|
$packageName = 'foobar';
|
||||||
|
|
||||||
|
$package1 = $this->createPackage('1.0.0');
|
||||||
|
$package2 = $this->createPackage('1.1.0-beta');
|
||||||
|
$packages = array($package1, $package2);
|
||||||
|
|
||||||
|
$pool = $this->createMockPool();
|
||||||
|
$pool->expects($this->once())
|
||||||
|
->method('whatProvides')
|
||||||
|
->with($packageName, null, true)
|
||||||
|
->will($this->returnValue($packages));
|
||||||
|
|
||||||
|
$versionSelector = new VersionSelector($pool);
|
||||||
|
$best = $versionSelector->findBestCandidate($packageName);
|
||||||
|
|
||||||
|
$this->assertSame($package1, $best, 'Latest most stable version should be returned (1.0.0)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHighestVersionIsReturned()
|
||||||
|
{
|
||||||
|
$packageName = 'foobar';
|
||||||
|
|
||||||
|
$package1 = $this->createPackage('1.0.0');
|
||||||
|
$package2 = $this->createPackage('1.1.0-beta');
|
||||||
|
$packages = array($package1, $package2);
|
||||||
|
|
||||||
|
$pool = $this->createMockPool();
|
||||||
|
$pool->expects($this->once())
|
||||||
|
->method('whatProvides')
|
||||||
|
->with($packageName, null, true)
|
||||||
|
->will($this->returnValue($packages));
|
||||||
|
|
||||||
|
$versionSelector = new VersionSelector($pool);
|
||||||
|
$best = $versionSelector->findBestCandidate($packageName, null, null, false);
|
||||||
|
|
||||||
|
$this->assertSame($package2, $best, 'Latest version should be returned (1.1.0-beta)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFalseReturnedOnNoPackages()
|
public function testFalseReturnedOnNoPackages()
|
||||||
|
@ -86,7 +151,7 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
||||||
$recommended = $versionSelector->findRecommendedRequireVersion($package);
|
$recommended = $versionSelector->findRecommendedRequireVersion($package);
|
||||||
|
|
||||||
// assert that the recommended version is what we expect
|
// assert that the recommended version is what we expect
|
||||||
$this->assertEquals($expectedVersion, $recommended);
|
$this->assertSame($expectedVersion, $recommended);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRecommendedRequireVersionPackages()
|
public function getRecommendedRequireVersionPackages()
|
||||||
|
@ -124,14 +189,10 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createMockPackage($version)
|
private function createPackage($version)
|
||||||
{
|
{
|
||||||
$package = $this->getMock('\Composer\Package\PackageInterface');
|
$parser = new VersionParser();
|
||||||
$package->expects($this->any())
|
return new Package('foo', $parser->normalize($version), $version);
|
||||||
->method('getVersion')
|
|
||||||
->will($this->returnValue($version));
|
|
||||||
|
|
||||||
return $package;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createMockPool()
|
private function createMockPool()
|
||||||
|
|
Loading…
Reference in New Issue