Merge pull request #10318 from Seldaek/ignore_upper_bounds

Add support for ignoring the upper bound of platform requirements using "name+" notation
main
Jordi Boggiano 2 years ago committed by GitHub
commit 005117dda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -79,7 +79,7 @@
],
"scripts": {
"compile": "@php -dphar.readonly=0 bin/compile",
"test": "simple-phpunit",
"test": "@php simple-phpunit",
"phpstan-setup": [
"@composer config platform --unset",
"@composer update",

@ -117,7 +117,10 @@ resolution.
See also the [`platform`](06-config.md#platform) config option.
* **--ignore-platform-req:** ignore a specific platform requirement(`php`,
`hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine
does not fulfill it. Multiple requirements can be ignored via wildcard.
does not fulfill it. Multiple requirements can be ignored via wildcard. Appending
a `+` makes it only ignore the upper-bound of the requirements. For example, if a package
requires `php: ^7`, then the option `--ignore-platform-req=php+` would allow installing on PHP8,
but installation on PHP 5.6 would still fail.
## update / u
@ -202,7 +205,10 @@ php composer.phar update vendor/package:2.0.1 vendor/package2:3.0.*
See also the [`platform`](06-config.md#platform) config option.
* **--ignore-platform-req:** ignore a specific platform requirement(`php`,
`hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine
does not fulfill it. Multiple requirements can be ignored via wildcard.
does not fulfill it. Multiple requirements can be ignored via wildcard. Appending
a `+` makes it only ignore the upper-bound of the requirements. For example, if a package
requires `php: ^7`, then the option `--ignore-platform-req=php+` would allow installing on PHP8,
but installation on PHP 5.6 would still fail.
* **--prefer-stable:** Prefer stable versions of dependencies.
* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
versions of requirements, generally used with `--prefer-stable`.

@ -15,6 +15,7 @@ namespace Composer\DependencyResolver;
use Composer\Package\CompletePackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
@ -242,19 +243,19 @@ class Problem
$ext = substr($packageName, 4);
$msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ';
if (extension_loaded($ext)) {
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0');
if (null === $version) {
return array($msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".');
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0');
if (null === $version) {
if (extension_loaded($ext)) {
return array(
$msg,
'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".',
);
}
$error = 'it has the wrong version ('.$version.') installed';
} else {
$error = 'it is missing from your system';
return array($msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.');
}
return array($msg, $error.'. Install or enable PHP\'s '.$ext.' extension.');
return array($msg, 'it has the wrong version installed ('.$version.').');
}
// handle linked libs
@ -431,11 +432,31 @@ class Problem
$available = $pool->whatProvides($packageName);
if (count($available)) {
$firstAvailable = reset($available);
$version = $firstAvailable->getPrettyVersion();
$extra = $firstAvailable->getExtra();
if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
$version .= '; ' . str_replace('Package ', '', $firstAvailable->getDescription());
$selected = null;
foreach ($available as $pkg) {
if ($pkg->getRepository() instanceof PlatformRepository) {
$selected = $pkg;
break;
}
}
if ($selected === null) {
$selected = reset($available);
}
// must be a package providing/replacing and not a real platform package
if ($selected->getName() !== $packageName) {
/** @var Link $link */
foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) {
if ($link->getTarget() === $packageName) {
return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString();
}
}
}
$version = $selected->getPrettyVersion();
$extra = $selected->getExtra();
if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
$version .= '; ' . str_replace('Package ', '', $selected->getDescription());
}
} else {
return null;

@ -12,6 +12,7 @@
namespace Composer\DependencyResolver;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Package\BasePackage;
@ -197,11 +198,14 @@ class RuleSetGenerator
}
foreach ($package->getRequires() as $link) {
$constraint = $link->getConstraint();
if ($platformRequirementFilter->isIgnored($link->getTarget())) {
continue;
} elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
$constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint);
}
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
$possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint);
$this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));
@ -225,11 +229,14 @@ class RuleSetGenerator
continue;
}
$constraint = $link->getConstraint();
if ($platformRequirementFilter->isIgnored($link->getTarget())) {
continue;
} elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
$constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint);
}
$conflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
$conflicts = $this->pool->whatProvides($link->getTarget(), $constraint);
foreach ($conflicts as $conflict) {
// define the conflict rule for regular packages, for alias packages it's only needed if the name
@ -277,6 +284,8 @@ class RuleSetGenerator
foreach ($request->getRequires() as $packageName => $constraint) {
if ($platformRequirementFilter->isIgnored($packageName)) {
continue;
} elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
$constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
}
$packages = $this->pool->whatProvides($packageName, $constraint);

@ -12,6 +12,7 @@
namespace Composer\DependencyResolver;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
@ -174,6 +175,8 @@ class Solver
foreach ($request->getRequires() as $packageName => $constraint) {
if ($platformRequirementFilter->isIgnored($packageName)) {
continue;
} elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
$constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
}
if (!$this->pool->whatProvides($packageName, $constraint)) {

@ -5,20 +5,40 @@ namespace Composer\Filter\PlatformRequirementFilter;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Interval;
use Composer\Semver\Intervals;
final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
/**
* @var string
*/
private $regexp;
private $ignoreRegex;
/**
* @var string
*/
private $ignoreUpperBoundRegex;
/**
* @param string[] $reqList
*/
public function __construct(array $reqList)
{
$this->regexp = BasePackage::packageNamesToRegexp($reqList);
$ignoreAll = $ignoreUpperBound = array();
foreach ($reqList as $req) {
if (substr($req, -1) === '+') {
$ignoreUpperBound[] = substr($req, 0, -1);
} else {
$ignoreAll[] = $req;
}
}
$this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll);
$this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound);
}
/**
@ -31,6 +51,33 @@ final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFi
return false;
}
return Preg::isMatch($this->regexp, $req);
return Preg::isMatch($this->ignoreRegex, $req);
}
/**
* @param string $req
* @return ConstraintInterface
*/
public function filterConstraint($req, ConstraintInterface $constraint)
{
if (!PlatformRepository::isPlatformPackage($req)) {
return $constraint;
}
if (!Preg::isMatch($this->ignoreUpperBoundRegex, $req)) {
return $constraint;
}
if (Preg::isMatch($this->ignoreRegex, $req)) {
return new MatchAllConstraint;
}
$intervals = Intervals::get($constraint);
$last = end($intervals['numeric']);
if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) {
$constraint = new MultiConstraint(array($constraint, new Constraint('>=', $last->getEnd()->getVersion())), false);
}
return $constraint;
}
}

@ -28,6 +28,7 @@ use Composer\DependencyResolver\SolverProblemsException;
use Composer\DependencyResolver\PolicyInterface;
use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
@ -807,15 +808,16 @@ class Installer
$rootRequires = array();
foreach ($requires as $req => $constraint) {
if ($constraint instanceof Link) {
$constraint = $constraint->getConstraint();
}
// skip platform requirements from the root package to avoid filtering out existing platform packages
if ($this->platformRequirementFilter->isIgnored($req)) {
continue;
} elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
$constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint);
}
if ($constraint instanceof Link) {
$rootRequires[$req] = $constraint->getConstraint();
} else {
$rootRequires[$req] = $constraint;
}
$rootRequires[$req] = $constraint;
}
$this->fixedRootPackage = clone $this->package;

@ -47,7 +47,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires a/a * -> satisfiable by a/a[1.0.0].
- a/a 1.0.0 requires ext-filter 2.0.0 -> it has the wrong version (7.4.0; overridden via config.platform, actual: %s) installed. Install or enable PHP's filter extension.
- a/a 1.0.0 requires ext-filter 2.0.0 -> it has the wrong version installed (7.4.0; overridden via config.platform, actual: %s).
To enable extensions, verify that they are enabled in your .ini files:
__inilist__

@ -119,7 +119,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 6
- Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension.
Problem 7
- Root composer.json requires PHP extension ext-xml 1002.* but it has the wrong version (%s) installed. Install or enable PHP's xml extension.
- Root composer.json requires PHP extension ext-xml 1002.* but it has the wrong version installed (%s).
Problem 8
- Root composer.json requires php 1 but your php version (%s) does not satisfy that requirement.
Problem 9
@ -161,4 +161,3 @@ Alternatively, you can run Composer with `--ignore-platform-req=ext-xml` to temp
Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
--EXPECT--

@ -0,0 +1,50 @@
--TEST--
Update with ignore-platform-req list ignoring upper bound of a dependency
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "a/a", "version": "1.0.1", "require": { "ext-foo-bar": "3.*" } },
{ "name": "b/b", "version": "1.0.1", "require": { "ext-foo-bar": "10.*" } }
]
}
],
"require": {
"a/a": "1.0.*",
"b/b": "1.0.*",
"php": "^4.3",
"ext-foo-baz": "9.0.0"
},
"provide": {
"ext-foo-bar": "5.0.3"
}
}
--INSTALLED--
[
{ "name": "a/a", "version": "1.0.0" }
]
--RUN--
update --ignore-platform-req=php+ --ignore-platform-req=ext-foo-bar+ --ignore-platform-req=ext-foo-baz+
--EXPECT-EXIT-CODE--
2
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires PHP extension ext-foo-baz [== 9.0.0.0 || >= 9.0.0.0] but it is missing from your system. Install or enable PHP's foo-baz extension.
Problem 2
- Root composer.json requires b/b 1.0.* -> satisfiable by b/b[1.0.1].
- b/b 1.0.1 requires ext-foo-bar 10.* -> it has the wrong version installed (5.0.3 provided by __root__ 1.0.0+no-version-set).
To enable extensions, verify that they are enabled in your .ini files:
__inilist__
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.
Alternatively, you can run Composer with `--ignore-platform-req=ext-foo-baz --ignore-platform-req=ext-foo-bar` to temporarily ignore these required extensions.
--EXPECT--

@ -77,12 +77,12 @@
{
"location": "Composer\\Test\\InstallerTest::testIntegrationWithRawPool",
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
"count": 1800
"count": 1820
},
{
"location": "Composer\\Test\\InstallerTest::testIntegrationWithPoolOptimizer",
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
"count": 1800
"count": 1820
},
{
"location": "Composer\\Test\\Package\\Archiver\\ArchivableFilesFinderTest::testManualExcludes",

Loading…
Cancel
Save