Add support for setting platform packages to false to disable them (#10308)

Fixes #9664
main
Jordi Boggiano 3 years ago committed by GitHub
parent 2c1ff41f8f
commit 91548d178b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -191,6 +191,9 @@ you may ignore it instead by passing `--ignore-platform-req=ext-foo` to `update`
extensions as if you ignore one now and a new package you add a month later also
requires it, you may introduce issues in production unknowingly.
If you have an extension installed locally but *not* on production, you may want
to artificially hide it from Composer using `{"ext-foo": false}`.
## vendor-dir
Defaults to `vendor`. You can install dependencies into a different directory if

@ -203,7 +203,7 @@
"type": "object",
"description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.",
"additionalProperties": {
"type": "string"
"type": ["string", "boolean"]
}
},
"vendor-dir": {

@ -704,7 +704,7 @@ EOT
return 0;
}
$this->configSource->addConfigSetting($settingKey, $values[0]);
$this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]);
return 0;
}

@ -996,7 +996,11 @@ EOT
}
$platformPkg = $platformRepo->findPackage($link->getTarget(), '*');
if (!$platformPkg) {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".';
} else {
$details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
}
continue;
}
if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) {

@ -20,6 +20,7 @@ use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\AliasPackage;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
@ -370,6 +371,11 @@ EOT
}
}
}
if ($repo === $platformRepo) {
foreach ($platformRepo->getDisabledPackages() as $name => $package) {
$packages[$type][$name] = $package;
}
}
}
}

@ -22,6 +22,7 @@ use Composer\Repository\LockArrayRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
/**
* Represents a problem detected while solving dependencies
@ -209,46 +210,62 @@ class Problem
*/
public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, $isVerbose, $packageName, ConstraintInterface $constraint = null)
{
// handle php/hhvm
if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion());
if (PlatformRepository::isPlatformPackage($packageName)) {
// handle php/php-*/hhvm
if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') {
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion());
$msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';
$msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';
if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) {
return array($msg, 'your HHVM version does not satisfy that requirement.');
}
if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) {
return array($msg, 'your HHVM version does not satisfy that requirement.');
}
if ($packageName === 'hhvm') {
return array($msg, 'HHVM was not detected on this machine, make sure it is in your PATH.');
}
if ($packageName === 'hhvm') {
return array($msg, 'HHVM was not detected on this machine, make sure it is in your PATH.');
}
return array($msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.');
}
if (null === $version) {
return array($msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".');
}
// handle php extensions
if (0 === stripos($packageName, 'ext-')) {
if (false !== strpos($packageName, ' ')) {
return array('- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.');
return array($msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.');
}
$ext = substr($packageName, 4);
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0');
// handle php extensions
if (0 === stripos($packageName, 'ext-')) {
if (false !== strpos($packageName, ' ')) {
return array('- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.');
}
$error = extension_loaded($ext) ? 'it has the wrong version ('.$version.') installed' : 'it is missing from your system';
$ext = substr($packageName, 4);
$msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ';
return array("- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ', $error.'. Install or enable PHP\'s '.$ext.' extension.');
}
if (extension_loaded($ext)) {
$version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) ?: '0');
// handle linked libs
if (0 === stripos($packageName, 'lib-')) {
if (strtolower($packageName) === 'lib-icu') {
$error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';
if (null === $version) {
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("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error);
return array($msg, $error.'. Install or enable PHP\'s '.$ext.' extension.');
}
return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.');
// handle linked libs
if (0 === stripos($packageName, 'lib-')) {
if (strtolower($packageName) === 'lib-icu') {
$error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';
return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error);
}
return array("- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.');
}
}
$lockedPackage = null;
@ -404,9 +421,9 @@ class Problem
}
/**
* @param string $version
* @param string $packageName
* @return string
* @param string $version the effective runtime version of the platform package
* @return ?string a version string or null if it appears the package was artificially disabled
*/
private static function getPlatformPackageVersion(Pool $pool, $packageName, $version)
{
@ -419,6 +436,8 @@ class Problem
if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
$version .= '; ' . str_replace('Package ', '', $firstAvailable->getDescription());
}
} else {
return null;
}
return $version;

@ -80,6 +80,13 @@ class ValidatingArrayLoader implements LoaderInterface
if (!empty($this->config['config']['platform'])) {
foreach ((array) $this->config['config']['platform'] as $key => $platform) {
if (false === $platform) {
continue;
}
if (!is_string($platform)) {
$this->errors[] = 'config.platform.' . $key . ' : invalid value ('.gettype($platform).' '.var_export($platform, true).'): expected string or false';
continue;
}
try {
$this->versionParser->normalize($platform);
} catch (\Exception $e) {

@ -48,23 +48,36 @@ class PlatformRepository extends ArrayRepository
*
* Keyed by package name (lowercased)
*
* @var array<string, array{name: string, version: string}>
* @var array<string, array{name: string, version: string|false}>
*/
private $overrides = array();
/**
* Stores which packages have been disabled and their actual version
*
* @var array<string, CompletePackageInterface>
*/
private $disabledPackages = array();
/** @var Runtime */
private $runtime;
/** @var HhvmDetector */
private $hhvmDetector;
/**
* @param array<string, string> $overrides
* @param array<string, string|false> $overrides
*/
public function __construct(array $packages = array(), array $overrides = array(), Runtime $runtime = null, HhvmDetector $hhvmDetector = null)
{
$this->runtime = $runtime ?: new Runtime();
$this->hhvmDetector = $hhvmDetector ?: new HhvmDetector();
foreach ($overrides as $name => $version) {
if (!is_string($version) && false !== $version) { // @phpstan-ignore-line
throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.gettype($version).' '.var_export($version, true));
}
if ($name === 'php' && $version === false) {
throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.');
}
$this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
}
parent::__construct($packages);
@ -75,6 +88,23 @@ class PlatformRepository extends ArrayRepository
return 'platform repo';
}
/**
* @param string $name
* @return boolean
*/
public function isPlatformPackageDisabled($name)
{
return isset($this->disabledPackages[$name]);
}
/**
* @return array<string, CompletePackageInterface>
*/
public function getDisabledPackages()
{
return $this->disabledPackages;
}
protected function initialize()
{
parent::initialize();
@ -89,7 +119,9 @@ class PlatformRepository extends ArrayRepository
throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
}
$this->addOverriddenPackage($override);
if ($override['version'] !== false) {
$this->addOverriddenPackage($override);
}
}
$prettyVersion = PluginInterface::PLUGIN_API_VERSION;
@ -494,8 +526,17 @@ class PlatformRepository extends ArrayRepository
*/
public function addPackage(PackageInterface $package)
{
if (!$package instanceof CompletePackage) {
throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package));
}
// Skip if overridden
if (isset($this->overrides[$package->getName()])) {
if ($this->overrides[$package->getName()]['version'] === false) {
$this->addDisabledPackage($package);
return;
}
$overrider = $this->findPackage($package->getName(), '*');
if ($package->getVersion() === $overrider->getVersion()) {
$actualText = 'same as actual';
@ -511,6 +552,11 @@ class PlatformRepository extends ArrayRepository
// Skip if PHP is overridden and we are adding a php-* package
if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) {
if (isset($this->overrides[$package->getName()]) && $this->overrides[$package->getName()]['version'] === false) {
$this->addDisabledPackage($package);
return;
}
$overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName());
if ($package->getVersion() === $overrider->getVersion()) {
$actualText = 'same as actual';
@ -546,6 +592,17 @@ class PlatformRepository extends ArrayRepository
return $package;
}
/**
* @return void
*/
private function addDisabledPackage(CompletePackage $package)
{
$package->setDescription($package->getDescription().'. <warning>Package disabled via config.platform</warning>');
$package->setExtra(array('config.platform' => true));
$this->disabledPackages[$package->getName()] = $package;
}
/**
* Parses the version and adds a new package to the repository
*

@ -0,0 +1,86 @@
--TEST--
Test the error output of solver problems for disabled platform packages. ext/php are well reported if present but disabled, lib packages are currently not handled as it is too complex.
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "dependency/pkg", "version": "1.0.0", "require": {"php-ipv6": "^8"} },
{ "name": "dependency/pkg2", "version": "1.0.0", "require": {"php-64bit": "^8"} },
{ "name": "dependency/pkg3", "version": "1.0.0", "require": {"lib-xml": "1002.*"} },
{ "name": "dependency/pkg4", "version": "1.0.0", "require": {"lib-icu": "1001.*"} },
{ "name": "dependency/pkg5", "version": "1.0.0", "require": {"ext-foobar": "1.0.0"} },
{ "name": "dependency/pkg6", "version": "1.0.0", "require": {"ext-pcre": "^8"} }
]
}
],
"require": {
"dependency/pkg": "1.*",
"dependency/pkg2": "1.*",
"dependency/pkg3": "1.*",
"dependency/pkg4": "1.*",
"dependency/pkg5": "1.*",
"dependency/pkg6": "1.*",
"php-64bit": "^8",
"php-ipv6": "^8",
"lib-xml": "1002.*",
"lib-icu": "1001.*",
"ext-foobar": "1.0.0",
"ext-pcre": "^8"
},
"config": {
"platform": {
"php-64bit": false,
"php-ipv6": "8.0.3",
"lib-xml": false,
"lib-icu": false,
"ext-foobar": false,
"ext-pcre": false
}
}
}
--RUN--
update
--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-64bit ^8 but the php-64bit package is disabled by your platform config. Enable it again with "composer config platform.php-64bit --unset".
Problem 2
- Root composer.json requires linked library lib-xml 1002.* but it has the wrong version installed or is missing from your system, make sure to load the extension providing it.
Problem 3
- Root composer.json requires linked library lib-icu 1001.* but it has the wrong version installed, try upgrading the intl extension.
Problem 4
- Root composer.json requires PHP extension ext-foobar 1.0.0 but it is missing from your system. Install or enable PHP's foobar extension.
Problem 5
- Root composer.json requires PHP extension ext-pcre ^8 but the ext-pcre package is disabled by your platform config. Enable it again with "composer config platform.ext-pcre --unset".
Problem 6
- Root composer.json requires dependency/pkg2 1.* -> satisfiable by dependency/pkg2[1.0.0].
- dependency/pkg2 1.0.0 requires php-64bit ^8 -> the php-64bit package is disabled by your platform config. Enable it again with "composer config platform.php-64bit --unset".
Problem 7
- Root composer.json requires dependency/pkg3 1.* -> satisfiable by dependency/pkg3[1.0.0].
- dependency/pkg3 1.0.0 requires lib-xml 1002.* -> it has the wrong version installed or is missing from your system, make sure to load the extension providing it.
Problem 8
- Root composer.json requires dependency/pkg4 1.* -> satisfiable by dependency/pkg4[1.0.0].
- dependency/pkg4 1.0.0 requires lib-icu 1001.* -> it has the wrong version installed, try upgrading the intl extension.
Problem 9
- Root composer.json requires dependency/pkg5 1.* -> satisfiable by dependency/pkg5[1.0.0].
- dependency/pkg5 1.0.0 requires ext-foobar 1.0.0 -> it is missing from your system. Install or enable PHP's foobar extension.
Problem 10
- Root composer.json requires dependency/pkg6 1.* -> satisfiable by dependency/pkg6[1.0.0].
- dependency/pkg6 1.0.0 requires ext-pcre ^8 -> the ext-pcre package is disabled by your platform config. Enable it again with "composer config platform.ext-pcre --unset".
To enable extensions, verify that they are enabled in your .ini files:
__inilist__
You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.
--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": 1776
"count": 1784
},
{
"location": "Composer\\Test\\InstallerTest::testIntegrationWithPoolOptimizer",
"message": "preg_match(): Passing null to parameter #4 ($flags) of type int is deprecated",
"count": 1776
"count": 1784
},
{
"location": "Composer\\Test\\Package\\Archiver\\ArchivableFilesFinderTest::testManualExcludes",

Loading…
Cancel
Save