From 8af76decf8799f9ebb7fa45990c95bfb13e817c3 Mon Sep 17 00:00:00 2001 From: MGChecker Date: Sat, 8 Sep 2018 02:02:53 +0200 Subject: [PATCH] registration: Let extensions add PHP version requirements While MediaWiki Core already sets requirements for PHP versions, it should be possible for extensions to tighten these requirements. This mirrors the PHP parameter of extension infoboxes as well. This change introduces a new 'platform' key (in addition to 'MediaWiki', 'skins' and 'extensions', where non-MediaWiki software requirements will be listed in the future, starting with a PHP version constraint. Further keys are supposed to be added to allow setting constraints against php extensions and other abilities of the platform. Bug: T197535 Change-Id: I6744cc0be2363b603331af9dc860eb8603a1a89a --- docs/extension.schema.v1.json | 13 +- docs/extension.schema.v2.json | 13 +- .../registration/ExtensionDependencyError.php | 8 ++ includes/registration/ExtensionRegistry.php | 3 +- includes/registration/VersionChecker.php | 85 +++++++++--- .../registration/ExtensionProcessorTest.php | 3 + .../registration/VersionCheckerTest.php | 122 ++++++++++++++---- 7 files changed, 207 insertions(+), 40 deletions(-) diff --git a/docs/extension.schema.v1.json b/docs/extension.schema.v1.json index 0ff169c3c2..e6ec971e51 100644 --- a/docs/extension.schema.v1.json +++ b/docs/extension.schema.v1.json @@ -55,13 +55,24 @@ }, "requires": { "type": "object", - "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", + "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", "additionalProperties": false, "properties": { "MediaWiki": { "type": "string", "description": "Version constraint string against MediaWiki core." }, + "platform": { + "type": "object", + "description": "Indicates version constraints against platform services.", + "additionalProperties": false, + "properties": { + "php": { + "type": "string", + "description": "Version constraint string against PHP." + } + } + }, "extensions": { "type": "object", "description": "Set of version constraint strings against specific extensions." diff --git a/docs/extension.schema.v2.json b/docs/extension.schema.v2.json index 7de5ed5f9a..93bf0d908a 100644 --- a/docs/extension.schema.v2.json +++ b/docs/extension.schema.v2.json @@ -56,13 +56,24 @@ }, "requires": { "type": "object", - "description": "Indicates what versions of MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", + "description": "Indicates what versions of PHP, MediaWiki core or extensions are required. This syntax may be extended in the future, for example to check dependencies between other services.", "additionalProperties": false, "properties": { "MediaWiki": { "type": "string", "description": "Version constraint string against MediaWiki core." }, + "platform": { + "type": "object", + "description": "Indicates version constraints against platform services.", + "additionalProperties": false, + "properties": { + "php": { + "type": "string", + "description": "Version constraint string against PHP." + } + } + }, "extensions": { "type": "object", "description": "Set of version constraint strings against specific extensions." diff --git a/includes/registration/ExtensionDependencyError.php b/includes/registration/ExtensionDependencyError.php index d380d07761..dfd598541d 100644 --- a/includes/registration/ExtensionDependencyError.php +++ b/includes/registration/ExtensionDependencyError.php @@ -48,6 +48,11 @@ class ExtensionDependencyError extends Exception { */ public $incompatibleCore = false; + /** + * @var bool + */ + public $incompatiblePhp = false; + /** * @param array $errors Each error has a 'msg' and 'type' key at minimum */ @@ -59,6 +64,9 @@ class ExtensionDependencyError extends Exception { case 'incompatible-core': $this->incompatibleCore = true; break; + case 'incompatible-php': + $this->incompatiblePhp = true; + break; case 'missing-skins': $this->missingSkins[] = $info['missing']; break; diff --git a/includes/registration/ExtensionRegistry.php b/includes/registration/ExtensionRegistry.php index 1f8a27e60e..3138b37a04 100644 --- a/includes/registration/ExtensionRegistry.php +++ b/includes/registration/ExtensionRegistry.php @@ -213,7 +213,8 @@ class ExtensionRegistry { $autoloadNamespaces = []; $autoloaderPaths = []; $processor = new ExtensionProcessor(); - $versionChecker = new VersionChecker( $wgVersion ); + $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; + $versionChecker = new VersionChecker( $wgVersion, $phpVersion ); $extDependencies = []; $incompatible = []; $warnings = false; diff --git a/includes/registration/VersionChecker.php b/includes/registration/VersionChecker.php index 1569e088b8..93b4a14301 100644 --- a/includes/registration/VersionChecker.php +++ b/includes/registration/VersionChecker.php @@ -35,6 +35,11 @@ class VersionChecker { */ private $coreVersion = false; + /** + * @var Constraint|bool representing PHP version + */ + private $phpVersion = false; + /** * @var array Loaded extensions */ @@ -48,9 +53,10 @@ class VersionChecker { /** * @param string $coreVersion Current version of core */ - public function __construct( $coreVersion ) { + public function __construct( $coreVersion, $phpVersion ) { $this->versionParser = new VersionParser(); $this->setCoreVersion( $coreVersion ); + $this->setPhpVersion( $phpVersion ); } /** @@ -82,6 +88,21 @@ class VersionChecker { } } + /** + * Set PHP version. + * + * @param string $phpVersion Current PHP version. Must be well-formed. + * @throws UnexpectedValueException + */ + private function setPhpVersion( $phpVersion ) { + // normalize to make this throw an exception if the version is invalid + $this->phpVersion = new Constraint( + '==', + $this->versionParser->normalize( $phpVersion ) + ); + $this->phpVersion->setPrettyString( $phpVersion ); + } + /** * Check all given dependencies if they are compatible with the named * installed extensions in the $credits array. @@ -90,6 +111,9 @@ class VersionChecker { * { * 'FooBar' => { * 'MediaWiki' => '>= 1.25.0', + * 'platform': { + * 'php': '>= 7.0.0' + * }, * 'extensions' => { * 'FooBaz' => '>= 1.25.0' * }, @@ -108,14 +132,47 @@ class VersionChecker { foreach ( $dependencies as $dependencyType => $values ) { switch ( $dependencyType ) { case ExtensionRegistry::MEDIAWIKI_CORE: - $mwError = $this->handleMediaWikiDependency( $values, $extension ); + $mwError = $this->handleDependency( + $this->coreVersion, + $values, + $extension + ); if ( $mwError !== false ) { $errors[] = [ - 'msg' => $mwError, + 'msg' => + "{$extension} is not compatible with the current MediaWiki " + . "core (version {$this->coreVersion->getPrettyString()}), " + . "it requires: $values." + , 'type' => 'incompatible-core', ]; } break; + case 'platform': + foreach ( $values as $dependency => $constraint ) { + if ( $dependency === 'php' ) { + $phpError = $this->handleDependency( + $this->phpVersion, + $constraint, + $extension + ); + if ( $phpError !== false ) { + $errors[] = [ + 'msg' => + "{$extension} is not compatible with the current PHP " + . "version {$this->phpVersion->getPrettyString()}), " + . "it requires: $constraint." + , + 'type' => 'incompatible-php', + ]; + } + } else { + // add other platform dependencies here + throw new UnexpectedValueException( 'Dependency type ' . $dependency . + ' unknown in ' . $extension ); + } + } + break; case 'extensions': case 'skins': foreach ( $values as $dependency => $constraint ) { @@ -138,29 +195,27 @@ class VersionChecker { } /** - * Handle a dependency to MediaWiki core. It will check, if a MediaWiki version constraint was - * set with self::setCoreVersion before this call (if not, it will return an empty array) and - * checks the version constraint given against it. + * Handle a simple dependency to MediaWiki core or PHP. See handleMediaWikiDependency and + * handlePhpDependency for details. * + * @param Constraint|bool $version The version installed * @param string $constraint The required version constraint for this dependency * @param string $checkedExt The Extension, which depends on this dependency - * @return bool|string false if no error, or a string with the message + * @return bool false if no error, true else */ - private function handleMediaWikiDependency( $constraint, $checkedExt ) { - if ( $this->coreVersion === false ) { - // Couldn't parse the core version, so we can't check anything + private function handleDependency( $version, $constraint, $checkedExt ) { + if ( $version === false ) { + // Couldn't parse the version, so we can't check anything return false; } // if the installed and required version are compatible, return an empty array if ( $this->versionParser->parseConstraints( $constraint ) - ->matches( $this->coreVersion ) ) { + ->matches( $version ) ) { return false; } - // otherwise mark this as incompatible. - return "{$checkedExt} is not compatible with the current " - . "MediaWiki core (version {$this->coreVersion->getPrettyString()}), it requires: " - . "$constraint."; + + return true; } /** diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index d9e091dc8d..71a3a4fa80 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -678,6 +678,9 @@ class ExtensionProcessorTest extends MediaWikiTestCase { $info = self::$default + [ 'requires' => [ 'MediaWiki' => '>= 1.25.0', + 'platform' => [ + 'php' => '>= 5.5.9' + ], 'extensions' => [ 'Bar' => '*' ] diff --git a/tests/phpunit/includes/registration/VersionCheckerTest.php b/tests/phpunit/includes/registration/VersionCheckerTest.php index b668a9adb0..20f97bf581 100644 --- a/tests/phpunit/includes/registration/VersionCheckerTest.php +++ b/tests/phpunit/includes/registration/VersionCheckerTest.php @@ -9,10 +9,10 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { use PHPUnit4And6Compat; /** - * @dataProvider provideCheck + * @dataProvider provideMediaWikiCheck */ - public function testCheck( $coreVersion, $constraint, $expected ) { - $checker = new VersionChecker( $coreVersion ); + public function testMediaWikiCheck( $coreVersion, $constraint, $expected ) { + $checker = new VersionChecker( $coreVersion, '7.0.0' ); $this->assertEquals( $expected, !(bool)$checker->checkArray( [ 'FakeExtension' => [ 'MediaWiki' => $constraint, @@ -20,7 +20,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { ] ) ); } - public static function provideCheck() { + public static function provideMediaWikiCheck() { return [ // [ $wgVersion, constraint, expected ] [ '1.25alpha', '>= 1.26', false ], @@ -44,11 +44,64 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { ]; } + /** + * @dataProvider providePhpValidCheck + */ + public function testPhpValidCheck( $phpVersion, $constraint, $expected ) { + $checker = new VersionChecker( '1.0.0', $phpVersion ); + $this->assertEquals( $expected, !(bool)$checker->checkArray( [ + 'FakeExtension' => [ + 'platform' => [ + 'php' => $constraint, + ], + ], + ] ) ); + } + + public static function providePhpValidCheck() { + return [ + // [ phpVersion, constraint, expected ] + [ '7.0.23', '>= 7.0.0', true ], + [ '7.0.23', '^7.1.0', false ], + [ '7.0.23', '7.0.23', true ], + ]; + } + + /** + * @expectedException UnexpectedValueException + */ + public function testPhpInvalidConstraint() { + $checker = new VersionChecker( '1.0.0', '7.0.0' ); + $checker->checkArray( [ + 'FakeExtension' => [ + 'platform' => [ + 'php' => 'totallyinvalid', + ], + ], + ] ); + } + + /** + * @dataProvider providePhpInvalidVersion + * @expectedException UnexpectedValueException + */ + public function testPhpInvalidVersion( $phpVersion ) { + $checker = new VersionChecker( '1.0.0', $phpVersion ); + } + + public static function providePhpInvalidVersion() { + return [ + // [ phpVersion ] + [ '7.abc' ], + [ '5.a.x' ], + ]; + } + /** * @dataProvider provideType */ public function testType( $given, $expected ) { - $checker = new VersionChecker( '1.0.0' ); + $checker = new VersionChecker( '1.0.0', '7.0.0' ); $checker->setLoadedExtensionsAndSkins( [ 'FakeDependency' => [ 'version' => '1.0.0', @@ -150,7 +203,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { * returns any error message. */ public function testInvalidConstraint() { - $checker = new VersionChecker( '1.0.0' ); + $checker = new VersionChecker( '1.0.0', '7.0.0' ); $checker->setLoadedExtensionsAndSkins( [ 'FakeDependency' => [ 'version' => 'not really valid', @@ -169,7 +222,7 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { ], ] ) ); - $checker = new VersionChecker( '1.0.0' ); + $checker = new VersionChecker( '1.0.0', '7.0.0' ); $checker->setLoadedExtensionsAndSkins( [ 'FakeDependency' => [ 'version' => '1.24.3', @@ -184,24 +237,49 @@ class VersionCheckerTest extends PHPUnit\Framework\TestCase { ] ); } - /** - * T197478 - */ - public function testInvalidDependency() { - $checker = new VersionChecker( '1.0.0' ); - $this->setExpectedException( UnexpectedValueException::class, - 'Dependency type skin unknown in FakeExtension' ); - $this->assertEquals( [ + public function provideInvalidDependency() { + return [ [ - 'type' => 'invalid-version', - 'msg' => 'FakeDependency does not have a valid version string.', + [ + 'FakeExtension' => [ + 'platform' => [ + 'undefinedPlatformDependency' => '*', + ], + ], + ], + 'undefinedPlatformDependency', ], - ], $checker->checkArray( [ - 'FakeExtension' => [ - 'skin' => [ - 'FakeSkin' => '*', + [ + [ + 'FakeExtension' => [ + 'undefinedDependencyType' => '*', + ], ], + 'undefinedDependencyType', ], - ] ) ); + // T197478 + [ + [ + 'FakeExtension' => [ + 'skin' => [ + 'FakeSkin' => '*', + ], + ], + ], + 'skin', + ], + ]; + } + + /** + * @dataProvider provideInvalidDependency + */ + public function testInvalidDependency( $depencency, $type ) { + $checker = new VersionChecker( '1.0.0', '7.0.0' ); + $this->setExpectedException( + UnexpectedValueException::class, + "Dependency type $type unknown in FakeExtension" + ); + $checker->checkArray( $depencency ); } } -- 2.20.1