1
0
Fork 0

Merge pull request #10318 from Seldaek/ignore_upper_bounds

Add support for ignoring the upper bound of platform requirements using "name+" notation
pull/10335/head
Jordi Boggiano 2021-12-07 14:07:53 +01:00 committed by GitHub
commit 005117dda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 169 additions and 32 deletions

View File

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

View File

@ -117,7 +117,10 @@ resolution.
See also the [`platform`](06-config.md#platform) config option. See also the [`platform`](06-config.md#platform) config option.
* **--ignore-platform-req:** ignore a specific platform requirement(`php`, * **--ignore-platform-req:** ignore a specific platform requirement(`php`,
`hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine `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 ## 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. See also the [`platform`](06-config.md#platform) config option.
* **--ignore-platform-req:** ignore a specific platform requirement(`php`, * **--ignore-platform-req:** ignore a specific platform requirement(`php`,
`hhvm`, `lib-*` and `ext-*`) and force the installation even if the local machine `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-stable:** Prefer stable versions of dependencies.
* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal * **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
versions of requirements, generally used with `--prefer-stable`. versions of requirements, generally used with `--prefer-stable`.

View File

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

View File

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

View File

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

View File

@ -5,20 +5,40 @@ namespace Composer\Filter\PlatformRequirementFilter;
use Composer\Package\BasePackage; use Composer\Package\BasePackage;
use Composer\Pcre\Preg; use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository; 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 final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface
{ {
/** /**
* @var string * @var string
*/ */
private $regexp; private $ignoreRegex;
/**
* @var string
*/
private $ignoreUpperBoundRegex;
/** /**
* @param string[] $reqList * @param string[] $reqList
*/ */
public function __construct(array $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 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;
} }
} }

View File

@ -28,6 +28,7 @@ use Composer\DependencyResolver\SolverProblemsException;
use Composer\DependencyResolver\PolicyInterface; use Composer\DependencyResolver\PolicyInterface;
use Composer\Downloader\DownloadManager; use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
@ -807,16 +808,17 @@ class Installer
$rootRequires = array(); $rootRequires = array();
foreach ($requires as $req => $constraint) { 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 // skip platform requirements from the root package to avoid filtering out existing platform packages
if ($this->platformRequirementFilter->isIgnored($req)) { if ($this->platformRequirementFilter->isIgnored($req)) {
continue; 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; $this->fixedRootPackage = clone $this->package;
$this->fixedRootPackage->setRequires(array()); $this->fixedRootPackage->setRequires(array());

View File

@ -47,7 +47,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 1 Problem 1
- Root composer.json requires a/a * -> satisfiable by a/a[1.0.0]. - 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: To enable extensions, verify that they are enabled in your .ini files:
__inilist__ __inilist__

View File

@ -119,7 +119,7 @@ Your requirements could not be resolved to an installable set of packages.
Problem 6 Problem 6
- Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension. - Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension.
Problem 7 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 Problem 8
- Root composer.json requires php 1 but your php version (%s) does not satisfy that requirement. - Root composer.json requires php 1 but your php version (%s) does not satisfy that requirement.
Problem 9 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. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
--EXPECT-- --EXPECT--

View File

@ -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--

View File

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