From a1368a3d9bbeb331c8558e1159514eaccfab2352 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Tue, 19 May 2015 18:45:10 -0700 Subject: [PATCH] registration: Version the extension.json schema Versioning the extension.json schema will allow us to make breaking changes to the schema in a non-breaking manner. Extensiosn and skins will set a 'manifest_version' value, stating which version of the schema the file is written for. Processor::extractInfo() will be passed the version number, and can switch behavior depending upon it. For backwards-compatability, a version number of 1 is assumed if none is set. The validateRegistrationFile.php script will emit a warning if this is the case. Bug: T99344 Change-Id: I2086a1465ceaeedd1ccc6804fda2c304ad16ffa0 --- docs/extension.schema.json | 5 ++++ includes/registration/ExtensionProcessor.php | 4 ++- includes/registration/ExtensionRegistry.php | 20 +++++++++++++- includes/registration/Processor.php | 3 ++- .../convertExtensionToRegistration.php | 3 ++- maintenance/validateRegistrationFile.php | 27 ++++++++++++++++--- .../registration/ExtensionProcessorTest.php | 12 ++++----- 7 files changed, 61 insertions(+), 13 deletions(-) diff --git a/docs/extension.schema.json b/docs/extension.schema.json index d5c17a1711..34cfe2c836 100644 --- a/docs/extension.schema.json +++ b/docs/extension.schema.json @@ -3,6 +3,11 @@ "description": "MediaWiki extension.json schema", "type": "object", "properties": { + "manifest_version": { + "type": "integer", + "description": "Version of the extension.json schema the extension.json file is in.", + "required": true + }, "name": { "type": "string", "description": "The extension's canonical name.", diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index b0398eb475..0a09ff5a42 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -81,6 +81,7 @@ class ExtensionProcessor implements Processor { 'config', 'ParserTestFiles', 'AutoloadClasses', + 'manifest_version', ); /** @@ -125,9 +126,10 @@ class ExtensionProcessor implements Processor { /** * @param string $path * @param array $info + * @param int $version manifest_version for info * @return array */ - public function extractInfo( $path, array $info ) { + public function extractInfo( $path, array $info, $version ) { $this->extractConfig( $info ); $this->extractHooks( $info ); $dir = dirname( $path ); diff --git a/includes/registration/ExtensionRegistry.php b/includes/registration/ExtensionRegistry.php index ac396991fc..1c36407527 100644 --- a/includes/registration/ExtensionRegistry.php +++ b/includes/registration/ExtensionRegistry.php @@ -11,6 +11,16 @@ */ class ExtensionRegistry { + /** + * Version of the highest supported manifest version + */ + const MANIFEST_VERSION = 1; + + /** + * Version of the oldest supported manifest version + */ + const OLDEST_MANIFEST_VERSION = 1; + /** * @var BagOStuff */ @@ -128,11 +138,19 @@ class ExtensionRegistry { if ( !is_array( $info ) ) { throw new Exception( "$path is not a valid JSON file." ); } + if ( !isset( $info['manifest_version' ] ) ) { + // For backwards-compatability, assume a version of 1 + $info['manifest_version'] = 1; + } + $version = $info['manifest_version']; + if ( $version < self::OLDEST_MANIFEST_VERSION || $version > self::MANIFEST_VERSION ) { + throw new Exception( "$path: unsupported manifest_version: {$version}" ); + } $autoload = $this->processAutoLoader( dirname( $path ), $info ); // Set up the autoloader now so custom processors will work $GLOBALS['wgAutoloadClasses'] += $autoload; $autoloadClasses += $autoload; - $processor->extractInfo( $path, $info ); + $processor->extractInfo( $path, $info, $version ); } $data = $processor->getExtractedInfo(); // Need to set this so we can += to it later diff --git a/includes/registration/Processor.php b/includes/registration/Processor.php index 391f1085b6..e1aaca73b7 100644 --- a/includes/registration/Processor.php +++ b/includes/registration/Processor.php @@ -16,9 +16,10 @@ interface Processor { * * @param string $path Absolute path of JSON file * @param array $info + * @param int $version manifest_version for info * @return array "credits" information to store */ - public function extractInfo( $path, array $info ); + public function extractInfo( $path, array $info, $version ); /** * @return array With following keys: diff --git a/maintenance/convertExtensionToRegistration.php b/maintenance/convertExtensionToRegistration.php index e0631a721d..8adae2d38c 100644 --- a/maintenance/convertExtensionToRegistration.php +++ b/maintenance/convertExtensionToRegistration.php @@ -118,7 +118,8 @@ class ConvertExtensionToRegistration extends Maintenance { } } $out += $this->json; - + // Put this at the bottom + $out['manifest_version'] = ExtensionRegistry::MANIFEST_VERSION; $type = $this->hasOption( 'skin' ) ? 'skin' : 'extension'; $fname = "{$this->dir}/$type.json"; $prettyJSON = FormatJson::encode( $out, "\t", FormatJson::ALL_OK ); diff --git a/maintenance/validateRegistrationFile.php b/maintenance/validateRegistrationFile.php index e7646610f4..08af11a341 100644 --- a/maintenance/validateRegistrationFile.php +++ b/maintenance/validateRegistrationFile.php @@ -12,18 +12,39 @@ class ValidateRegistrationFile extends Maintenance { $this->error( 'The JsonSchema library cannot be found, please install it through composer.', 1 ); } - $retriever = new JsonSchema\Uri\UriRetriever(); - $schema = $retriever->retrieve('file://' . dirname( __DIR__ ) . '/docs/extension.schema.json' ); $path = $this->getArg( 0 ); $data = json_decode( file_get_contents( $path ) ); if ( !is_object( $data ) ) { $this->error( "$path is not a valid JSON file.", 1 ); } + if ( !isset( $data->manifest_version ) ) { + $this->output("Warning: No manifest_version set, assuming 1.\n" ); + // For backwards-compatability assume 1 + $data->manifest_version = 1; + } + $version = $data->manifest_version; + if ( $version !== ExtensionRegistry::MANIFEST_VERSION ) { + $schemaPath = dirname( __DIR__ ) . "/docs/extension.schema.v$version.json"; + } else { + $schemaPath = dirname( __DIR__ ) . '/docs/extension.schema.json'; + } + + if ( $version < ExtensionRegistry::OLDEST_MANIFEST_VERSION + || $version > ExtensionRegistry::MANIFEST_VERSION + ) { + $this->error( "Error: $path is using a non-supported schema version, it should use " + . ExtensionRegistry::MANIFEST_VERSION, 1 ); + } elseif ( $version < ExtensionRegistry::MANIFEST_VERSION ) { + $this->output( "Warning: $path is using a deprecated schema, and should be updated to " + . ExtensionRegistry::MANIFEST_VERSION . "\n" ); + } + $retriever = new JsonSchema\Uri\UriRetriever(); + $schema = $retriever->retrieve('file://' . $schemaPath ); $validator = new JsonSchema\Validator(); $validator->check( $data, $schema ); if ( $validator->isValid() ) { - $this->output( "$path validates against the schema!\n" ); + $this->output( "$path validates against the version $version schema!\n" ); } else { foreach ( $validator->getErrors() as $error ) { $this->output( "[{$error['property']}] {$error['message']}\n" ); diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index 94744969b7..2df5568a39 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -28,7 +28,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '@metadata' => array( 'foobarbaz' ), 'AnAttribute' => array( 'omg' ), 'AutoloadClasses' => array( 'FooBar' => 'includes/FooBar.php' ), - ) ); + ), 1 ); $extracted = $processor->getExtractedInfo(); $attributes = $extracted['attributes']; @@ -95,7 +95,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testRegisterHooks( $pre, $info, $expected ) { $processor = new MockExtensionProcessor( array( 'wgHooks' => $pre ) ); - $processor->extractInfo( $this->dir, $info ); + $processor->extractInfo( $this->dir, $info, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( $expected, $extracted['globals']['wgHooks'] ); } @@ -112,7 +112,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { '@IGNORED' => 'yes', ), ) + self::$default; - $processor->extractInfo( $this->dir, $info ); + $processor->extractInfo( $this->dir, $info, 1 ); $extracted = $processor->getExtractedInfo(); $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] ); $this->assertEquals( 10, $extracted['globals']['wgFoo'] ); @@ -149,7 +149,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtracttExtensionMessagesFiles( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -177,7 +177,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractMessagesDirs( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); @@ -190,7 +190,7 @@ class ExtensionProcessorTest extends MediaWikiTestCase { */ public function testExtractResourceLoaderModules( $input, $expected ) { $processor = new ExtensionProcessor(); - $processor->extractInfo( $this->dir, $input + self::$default ); + $processor->extractInfo( $this->dir, $input + self::$default, 1 ); $out = $processor->getExtractedInfo(); foreach ( $expected as $key => $value ) { $this->assertEquals( $value, $out['globals'][$key] ); -- 2.20.1