You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
10 KiB
PHP
254 lines
10 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of Composer.
|
|
*
|
|
* (c) Nils Adermann <naderman@naderman.de>
|
|
* Jordi Boggiano <j.boggiano@seld.be>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Composer\Package\Version;
|
|
|
|
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
|
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
|
|
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
|
|
use Composer\Package\BasePackage;
|
|
use Composer\Package\AliasPackage;
|
|
use Composer\Package\PackageInterface;
|
|
use Composer\Composer;
|
|
use Composer\Package\Loader\ArrayLoader;
|
|
use Composer\Package\Dumper\ArrayDumper;
|
|
use Composer\Pcre\Preg;
|
|
use Composer\Repository\RepositorySet;
|
|
use Composer\Repository\PlatformRepository;
|
|
use Composer\Semver\Constraint\Constraint;
|
|
use Composer\Semver\Constraint\ConstraintInterface;
|
|
|
|
/**
|
|
* Selects the best possible version for a package
|
|
*
|
|
* @author Ryan Weaver <ryan@knpuniversity.com>
|
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
|
*/
|
|
class VersionSelector
|
|
{
|
|
/** @var RepositorySet */
|
|
private $repositorySet;
|
|
|
|
/** @var array<string, ConstraintInterface[]> */
|
|
private $platformConstraints = array();
|
|
|
|
/** @var VersionParser */
|
|
private $parser;
|
|
|
|
/**
|
|
* @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages
|
|
*/
|
|
public function __construct(RepositorySet $repositorySet, PlatformRepository $platformRepo = null)
|
|
{
|
|
$this->repositorySet = $repositorySet;
|
|
if ($platformRepo) {
|
|
foreach ($platformRepo->getPackages() as $package) {
|
|
$this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a package name and optional version, returns the latest PackageInterface
|
|
* that matches.
|
|
*
|
|
* @param string $packageName
|
|
* @param string $targetPackageVersion
|
|
* @param string $preferredStability
|
|
* @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
|
|
* @param int $repoSetFlags*
|
|
* @return PackageInterface|false
|
|
*/
|
|
public function findBestCandidate($packageName, $targetPackageVersion = null, $preferredStability = 'stable', $platformRequirementFilter = null, $repoSetFlags = 0)
|
|
{
|
|
if (!isset(BasePackage::$stabilities[$preferredStability])) {
|
|
// If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version
|
|
throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability);
|
|
}
|
|
|
|
if (null === $platformRequirementFilter) {
|
|
$platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
|
|
} elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) {
|
|
trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED);
|
|
$platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter);
|
|
}
|
|
|
|
$constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
|
|
$candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags);
|
|
|
|
if ($this->platformConstraints && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) {
|
|
$platformConstraints = $this->platformConstraints;
|
|
$candidates = array_filter($candidates, function ($pkg) use ($platformConstraints, $platformRequirementFilter) {
|
|
$reqs = $pkg->getRequires();
|
|
|
|
foreach ($reqs as $name => $link) {
|
|
if (!$platformRequirementFilter->isIgnored($name)) {
|
|
if (isset($platformConstraints[$name])) {
|
|
foreach ($platformConstraints[$name] as $constraint) {
|
|
if ($link->getConstraint()->matches($constraint)) {
|
|
continue 2;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} elseif (PlatformRepository::isPlatformPackage($name)) {
|
|
// Package requires a platform package that is unknown on current platform.
|
|
// It means that current platform cannot validate this constraint and so package is not installable.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
if (!$candidates) {
|
|
return false;
|
|
}
|
|
|
|
// select highest version if we have many
|
|
$package = reset($candidates);
|
|
$minPriority = BasePackage::$stabilities[$preferredStability];
|
|
foreach ($candidates as $candidate) {
|
|
$candidatePriority = $candidate->getStabilityPriority();
|
|
$currentPriority = $package->getStabilityPriority();
|
|
|
|
// candidate is less stable than our preferred stability,
|
|
// and current package is more stable than candidate, skip it
|
|
if ($minPriority < $candidatePriority && $currentPriority < $candidatePriority) {
|
|
continue;
|
|
}
|
|
|
|
// candidate is less stable than our preferred stability,
|
|
// and current package is less stable than candidate, select candidate
|
|
if ($minPriority < $candidatePriority && $candidatePriority < $currentPriority) {
|
|
$package = $candidate;
|
|
continue;
|
|
}
|
|
|
|
// candidate is more stable than our preferred stability,
|
|
// and current package is less stable than preferred stability, select candidate
|
|
if ($minPriority >= $candidatePriority && $minPriority < $currentPriority) {
|
|
$package = $candidate;
|
|
continue;
|
|
}
|
|
|
|
// select highest version of the two
|
|
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
|
|
$package = $candidate;
|
|
}
|
|
}
|
|
|
|
// if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias
|
|
if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
|
|
$package = $package->getAliasOf();
|
|
}
|
|
|
|
return $package;
|
|
}
|
|
|
|
/**
|
|
* Given a concrete version, this returns a ^ constraint (when possible)
|
|
* that should be used, for example, in composer.json.
|
|
*
|
|
* For example:
|
|
* * 1.2.1 -> ^1.2
|
|
* * 1.2 -> ^1.2
|
|
* * v3.2.1 -> ^3.2
|
|
* * 2.0-beta.1 -> ^2.0@beta
|
|
* * dev-master -> ^2.1@dev (dev version with alias)
|
|
* * dev-master -> dev-master (dev versions are untouched)
|
|
*
|
|
* @param PackageInterface $package
|
|
* @return string
|
|
*/
|
|
public function findRecommendedRequireVersion(PackageInterface $package)
|
|
{
|
|
// Extensions which are versioned in sync with PHP should rather be required as "*" to simplify
|
|
// the requires and have only one required version to change when bumping the php requirement
|
|
if (0 === strpos($package->getName(), 'ext-')) {
|
|
$phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
|
|
$extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
|
|
if ($phpVersion === $extVersion) {
|
|
return '*';
|
|
}
|
|
}
|
|
|
|
$version = $package->getVersion();
|
|
if (!$package->isDev()) {
|
|
return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
|
|
}
|
|
|
|
$loader = new ArrayLoader($this->getParser());
|
|
$dumper = new ArrayDumper();
|
|
$extra = $loader->getBranchAlias($dumper->dump($package));
|
|
if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) {
|
|
$extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count);
|
|
if ($count) {
|
|
$extra = str_replace('.9999999', '.0', $extra);
|
|
|
|
return $this->transformVersion($extra, $extra, 'dev');
|
|
}
|
|
}
|
|
|
|
return $package->getPrettyVersion();
|
|
}
|
|
|
|
/**
|
|
* @param string $version
|
|
* @param string $prettyVersion
|
|
* @param string $stability
|
|
*
|
|
* @return string
|
|
*/
|
|
private function transformVersion($version, $prettyVersion, $stability)
|
|
{
|
|
// attempt to transform 2.1.1 to 2.1
|
|
// this allows you to upgrade through minor versions
|
|
$semanticVersionParts = explode('.', $version);
|
|
|
|
// check to see if we have a semver-looking version
|
|
if (count($semanticVersionParts) == 4 && Preg::isMatch('{^0\D?}', $semanticVersionParts[3])) {
|
|
// remove the last parts (i.e. the patch version number and any extra)
|
|
if ($semanticVersionParts[0] === '0') {
|
|
unset($semanticVersionParts[3]);
|
|
} else {
|
|
unset($semanticVersionParts[2], $semanticVersionParts[3]);
|
|
}
|
|
$version = implode('.', $semanticVersionParts);
|
|
} else {
|
|
return $prettyVersion;
|
|
}
|
|
|
|
// append stability flag if not default
|
|
if ($stability != 'stable') {
|
|
$version .= '@'.$stability;
|
|
}
|
|
|
|
// 2.1 -> ^2.1
|
|
return '^' . $version;
|
|
}
|
|
|
|
/**
|
|
* @return VersionParser
|
|
*/
|
|
private function getParser()
|
|
{
|
|
if ($this->parser === null) {
|
|
$this->parser = new VersionParser();
|
|
}
|
|
|
|
return $this->parser;
|
|
}
|
|
}
|