diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/tools/json_schema_compiler | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/tools/json_schema_compiler')
19 files changed, 1169 insertions, 53 deletions
diff --git a/chromium/tools/json_schema_compiler/cc_generator.py b/chromium/tools/json_schema_compiler/cc_generator.py index 9935e1cc399..371c60d3032 100644 --- a/chromium/tools/json_schema_compiler/cc_generator.py +++ b/chromium/tools/json_schema_compiler/cc_generator.py @@ -784,7 +784,9 @@ class _Generator(object): self._type_helper.GetEnumNoneValue(type_))) .Concat(self._GenerateError( '\"\'%%(key)s\': expected \\"' + - '\\" or \\"'.join(self._type_helper.FollowRef(type_).enum_values) + + '\\" or \\"'.join( + enum_value.name + for enum_value in self._type_helper.FollowRef(type_).enum_values) + '\\", got \\"" + %s + "\\""' % enum_as_string)) .Append('return %s;' % failure_value) .Eblock('}') @@ -820,7 +822,7 @@ class _Generator(object): c.Sblock('switch (enum_param) {') for enum_value in self._type_helper.FollowRef(type_).enum_values: (c.Append('case %s: ' % self._type_helper.GetEnumValue(type_, enum_value)) - .Append(' return "%s";' % enum_value)) + .Append(' return "%s";' % enum_value.name)) (c.Append('case %s:' % self._type_helper.GetEnumNoneValue(type_)) .Append(' return "";') .Eblock('}') @@ -848,7 +850,7 @@ class _Generator(object): # This is broken up into all ifs with no else ifs because we get # "fatal error C1061: compiler limit : blocks nested too deeply" # on Windows. - (c.Append('if (enum_string == "%s")' % enum_value) + (c.Append('if (enum_string == "%s")' % enum_value.name) .Append(' return %s;' % self._type_helper.GetEnumValue(type_, enum_value))) (c.Append('return %s;' % self._type_helper.GetEnumNoneValue(type_)) diff --git a/chromium/tools/json_schema_compiler/code.py b/chromium/tools/json_schema_compiler/code.py index 3622237a84a..8ce6afab675 100644 --- a/chromium/tools/json_schema_compiler/code.py +++ b/chromium/tools/json_schema_compiler/code.py @@ -133,6 +133,7 @@ class Code(object): """ return '\n'.join([l.value for l in self._code]) + class Line(object): """A line of code. """ diff --git a/chromium/tools/json_schema_compiler/compiler.py b/chromium/tools/json_schema_compiler/compiler.py index ddab77eadd7..21281fcdabf 100755 --- a/chromium/tools/json_schema_compiler/compiler.py +++ b/chromium/tools/json_schema_compiler/compiler.py @@ -26,11 +26,12 @@ from cpp_type_generator import CppTypeGenerator from dart_generator import DartGenerator import json_schema from model import Model +from ppapi_generator import PpapiGenerator from schema_loader import SchemaLoader # Names of supported code generators, as specified on the command-line. # First is default. -GENERATORS = ['cpp', 'cpp-bundle', 'dart'] +GENERATORS = ['cpp', 'cpp-bundle', 'dart', 'ppapi'] def GenerateSchema(generator, filenames, @@ -38,15 +39,14 @@ def GenerateSchema(generator, destdir, root_namespace, dart_overrides_dir): - schema_loader = SchemaLoader(os.path.dirname(os.path.relpath( - os.path.normpath(filenames[0]), root))) + schema_loader = SchemaLoader( + os.path.dirname(os.path.relpath(os.path.normpath(filenames[0]), root)), + os.path.dirname(filenames[0])) # Merge the source files into a single list of schemas. api_defs = [] for filename in filenames: schema = os.path.normpath(filename) - schema_filename, schema_extension = os.path.splitext(schema) - path, short_filename = os.path.split(schema_filename) - api_def = schema_loader.LoadSchema(schema) + api_def = schema_loader.LoadSchema(os.path.split(schema)[1]) # If compiling the C++ model code, delete 'nocompile' nodes. if generator == 'cpp': @@ -88,7 +88,8 @@ def GenerateSchema(generator, api_model, api_defs, type_generator, - root_namespace) + root_namespace, + namespace.source_file_dir) generators = [ ('generated_api.cc', cpp_bundle_generator.api_cc_generator), ('generated_api.h', cpp_bundle_generator.api_h_generator), @@ -106,6 +107,12 @@ def GenerateSchema(generator, ('%s.dart' % namespace.unix_name, DartGenerator( dart_overrides_dir)) ] + elif generator == 'ppapi': + generator = PpapiGenerator() + generators = [ + (os.path.join('api', 'ppb_%s.idl' % namespace.unix_name), + generator.idl_generator), + ] else: raise Exception('Unrecognised generator %s' % generator) diff --git a/chromium/tools/json_schema_compiler/cpp_bundle_generator.py b/chromium/tools/json_schema_compiler/cpp_bundle_generator.py index c713d555f72..1cadff8348d 100644 --- a/chromium/tools/json_schema_compiler/cpp_bundle_generator.py +++ b/chromium/tools/json_schema_compiler/cpp_bundle_generator.py @@ -12,8 +12,6 @@ import json import os import re -# TODO(miket/asargent) - parameterize this. -SOURCE_BASE_PATH = 'chrome/common/extensions/api' def _RemoveDescriptions(node): """Returns a copy of |schema| with "description" fields removed. @@ -36,12 +34,19 @@ class CppBundleGenerator(object): """This class contains methods to generate code based on multiple schemas. """ - def __init__(self, root, model, api_defs, cpp_type_generator, cpp_namespace): + def __init__(self, + root, + model, + api_defs, + cpp_type_generator, + cpp_namespace, + source_file_dir): self._root = root self._model = model self._api_defs = api_defs self._cpp_type_generator = cpp_type_generator self._cpp_namespace = cpp_namespace + self._source_file_dir = source_file_dir self.api_cc_generator = _APICCGenerator(self) self.api_h_generator = _APIHGenerator(self) @@ -57,8 +62,8 @@ class CppBundleGenerator(object): c = code.Code() c.Append(cpp_util.CHROMIUM_LICENSE) c.Append() - c.Append(cpp_util.GENERATED_BUNDLE_FILE_MESSAGE % SOURCE_BASE_PATH) - ifndef_name = cpp_util.GenerateIfndefName(SOURCE_BASE_PATH, file_base) + c.Append(cpp_util.GENERATED_BUNDLE_FILE_MESSAGE % self._source_file_dir) + ifndef_name = cpp_util.GenerateIfndefName(self._source_file_dir, file_base) c.Append() c.Append('#ifndef %s' % ifndef_name) c.Append('#define %s' % ifndef_name) @@ -79,9 +84,15 @@ class CppBundleGenerator(object): for platform in model_object.platforms: if platform == Platforms.CHROMEOS: ifdefs.append('defined(OS_CHROMEOS)') + elif platform == Platforms.LINUX: + ifdefs.append('defined(OS_LINUX)') + elif platform == Platforms.MAC: + ifdefs.append('defined(OS_MACOSX)') + elif platform == Platforms.WIN: + ifdefs.append('defined(OS_WIN)') else: raise ValueError("Unsupported platform ifdef: %s" % platform.name) - return ' and '.join(ifdefs) + return ' || '.join(ifdefs) def _GenerateRegisterFunctions(self, namespace_name, function): c = code.Code() @@ -165,7 +176,7 @@ class _APICCGenerator(object): c = code.Code() c.Append(cpp_util.CHROMIUM_LICENSE) c.Append() - c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH, + c.Append('#include "%s"' % (os.path.join(self._bundle._source_file_dir, 'generated_api.h'))) c.Append() for namespace in self._bundle._model.namespaces.values(): @@ -248,7 +259,7 @@ class _SchemasCCGenerator(object): c = code.Code() c.Append(cpp_util.CHROMIUM_LICENSE) c.Append() - c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH, + c.Append('#include "%s"' % (os.path.join(self._bundle._source_file_dir, 'generated_schemas.h'))) c.Append() c.Append('#include "base/lazy_instance.h"') diff --git a/chromium/tools/json_schema_compiler/cpp_type_generator.py b/chromium/tools/json_schema_compiler/cpp_type_generator.py index 4c0306d18bb..d485b46c5cd 100644 --- a/chromium/tools/json_schema_compiler/cpp_type_generator.py +++ b/chromium/tools/json_schema_compiler/cpp_type_generator.py @@ -65,7 +65,7 @@ class CppTypeGenerator(object): e.g VAR_STRING """ value = '%s_%s' % (self.FollowRef(type_).unix_name.upper(), - cpp_util.Classname(enum_value.upper())) + cpp_util.Classname(enum_value.name.upper())) # To avoid collisions with built-in OS_* preprocessor definitions, we add a # trailing slash to enum names that start with OS_. if value.startswith("OS_"): diff --git a/chromium/tools/json_schema_compiler/cpp_util.py b/chromium/tools/json_schema_compiler/cpp_util.py index 2d493439344..1e7c3707883 100644 --- a/chromium/tools/json_schema_compiler/cpp_util.py +++ b/chromium/tools/json_schema_compiler/cpp_util.py @@ -24,6 +24,10 @@ GENERATED_BUNDLE_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITIONS IN // %s // DO NOT EDIT. """ +GENERATED_FEATURE_MESSAGE = """// GENERATED FROM THE FEATURE DEFINITIONS IN +// %s +// DO NOT EDIT. +""" def Classname(s): """Translates a namespace name or function name into something more @@ -118,3 +122,18 @@ def CloseNamespace(namespace): for component in reversed(namespace.split('::')): c.Append('} // namespace %s' % component) return c + + +def ConstantName(feature_name): + """Returns a kName for a feature's name. + """ + return ('k' + ''.join(word[0].upper() + word[1:] + for word in feature_name.replace('.', ' ').split())) + + +def CamelCase(unix_name): + return ''.join(word.capitalize() for word in unix_name.split('_')) + + +def ClassName(filepath): + return CamelCase(os.path.split(filepath)[1]) diff --git a/chromium/tools/json_schema_compiler/features_cc_generator.py b/chromium/tools/json_schema_compiler/features_cc_generator.py new file mode 100644 index 00000000000..d3b3717eb92 --- /dev/null +++ b/chromium/tools/json_schema_compiler/features_cc_generator.py @@ -0,0 +1,95 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os.path + +from code import Code +import cpp_util + + +class CCGenerator(object): + def Generate(self, feature_defs, source_file, namespace): + return _Generator(feature_defs, source_file, namespace).Generate() + + +class _Generator(object): + """A .cc generator for features. + """ + def __init__(self, feature_defs, source_file, namespace): + self._feature_defs = feature_defs + self._source_file = source_file + self._source_file_filename, _ = os.path.splitext(source_file) + self._class_name = cpp_util.ClassName(self._source_file_filename) + self._namespace = namespace + + def Generate(self): + """Generates a Code object for features. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FEATURE_MESSAGE % self._source_file) + .Append() + .Append('#include <string>') + .Append() + .Append('#include "%s.h"' % self._source_file_filename) + .Append() + .Append('#include "base/logging.h"') + .Append() + .Concat(cpp_util.OpenNamespace(self._namespace)) + .Append() + ) + + # Generate the constructor. + (c.Append('%s::%s() {' % (self._class_name, self._class_name)) + .Sblock() + ) + for feature in self._feature_defs: + c.Append('features_["%s"] = %s;' + % (feature.name, cpp_util.ConstantName(feature.name))) + (c.Eblock() + .Append('}') + .Append() + ) + + # Generate the ToString function. + (c.Append('const char* %s::ToString(' + '%s::ID id) const {' % (self._class_name, self._class_name)) + .Sblock() + .Append('switch (id) {') + .Sblock() + ) + for feature in self._feature_defs: + c.Append('case %s: return "%s";' % + (cpp_util.ConstantName(feature.name), feature.name)) + (c.Append('case kUnknown: break;') + .Append('case kEnumBoundary: break;') + .Eblock() + .Append('}') + .Append('NOTREACHED();') + .Append('return "";') + ) + (c.Eblock() + .Append('}') + .Append() + ) + + # Generate the FromString function. + + (c.Append('%s::ID %s::FromString(' + 'const std::string& id) const {' + % (self._class_name, self._class_name)) + .Sblock() + .Append('std::map<std::string, %s::ID>::const_iterator it' + ' = features_.find(id);' % self._class_name) + .Append('if (it == features_.end())') + .Append(' return kUnknown;') + .Append('return it->second;') + .Eblock() + .Append('}') + .Append() + .Cblock(cpp_util.CloseNamespace(self._namespace)) + ) + + return c diff --git a/chromium/tools/json_schema_compiler/features_compiler.py b/chromium/tools/json_schema_compiler/features_compiler.py new file mode 100755 index 00000000000..1e4e81ada76 --- /dev/null +++ b/chromium/tools/json_schema_compiler/features_compiler.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generator for C++ features from json files. + +Usage example: + features_compiler.py --destdir gen --root /home/Work/src _permissions.json +""" + +import optparse +import os + +from schema_loader import SchemaLoader +from features_cc_generator import CCGenerator +from features_h_generator import HGenerator +from model import CreateFeature + + +def _GenerateSchema(filename, root, destdir, namespace): + """Generates C++ features files from the json file |filename|. + """ + # Load in the feature permissions from the JSON file. + schema = os.path.normpath(filename) + schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(schema, root)), + os.path.dirname(schema)) + schema_filename = os.path.splitext(schema)[0] + feature_defs = schema_loader.LoadSchema(schema) + + # Generate a list of the features defined and a list of their models. + feature_list = [] + for feature_def, feature in feature_defs.iteritems(): + feature_list.append(CreateFeature(feature_def, feature)) + + source_file_dir, _ = os.path.split(schema) + relpath = os.path.relpath(os.path.normpath(source_file_dir), root) + full_path = os.path.join(relpath, schema) + + generators = [ + ('%s.cc' % schema_filename, CCGenerator()), + ('%s.h' % schema_filename, HGenerator()) + ] + + # Generate and output the code for all features. + output_code = [] + for filename, generator in generators: + code = generator.Generate(feature_list, full_path, namespace).Render() + if destdir: + with open(os.path.join(destdir, relpath, filename), 'w') as f: + f.write(code) + output_code += [filename, '', code, ''] + + return '\n'.join(output_code) + + +if __name__ == '__main__': + parser = optparse.OptionParser( + description='Generates a C++ features model from JSON schema', + usage='usage: %prog [option]... schema') + parser.add_option('-r', '--root', default='.', + help='logical include root directory. Path to schema files from ' + 'specified dir will be the include path.') + parser.add_option('-d', '--destdir', + help='root directory to output generated files.') + parser.add_option('-n', '--namespace', default='generated_features', + help='C++ namespace for generated files. e.g extensions::api.') + (opts, filenames) = parser.parse_args() + + # Only one file is currently specified. + if len(filenames) != 1: + raise ValueError('One (and only one) file is required (for now).') + + result = _GenerateSchema(filenames[0], opts.root, opts.destdir, + opts.namespace) + if not opts.destdir: + print result diff --git a/chromium/tools/json_schema_compiler/features_h_generator.py b/chromium/tools/json_schema_compiler/features_h_generator.py new file mode 100644 index 00000000000..025f734fd95 --- /dev/null +++ b/chromium/tools/json_schema_compiler/features_h_generator.py @@ -0,0 +1,95 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os.path + +from code import Code +import cpp_util + + +class HGenerator(object): + def Generate(self, features, source_file, namespace): + return _Generator(features, source_file, namespace).Generate() + + +class _Generator(object): + """A .cc generator for features. + """ + def __init__(self, features, source_file, namespace): + self._feature_defs = features + self._source_file = source_file + self._source_file_filename, _ = os.path.splitext(source_file) + self._class_name = cpp_util.ClassName(self._source_file_filename) + self._namespace = namespace + + def Generate(self): + """Generates a Code object for features. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FEATURE_MESSAGE % self._source_file) + .Append() + ) + ifndef_name = cpp_util.GenerateIfndefName(self._source_file_filename, + self._class_name) + (c.Append('#ifndef %s' % ifndef_name) + .Append('#define %s' % ifndef_name) + .Append() + ) + + (c.Append('#include <map>') + .Append('#include <string>') + .Append() + .Concat(cpp_util.OpenNamespace(self._namespace)) + .Append() + ) + + (c.Append('class %s {' % self._class_name) + .Append(' public:') + .Sblock() + .Concat(self._GeneratePublicBody()) + .Eblock() + .Append(' private:') + .Sblock() + .Concat(self._GeneratePrivateBody()) + .Eblock('};') + .Append() + .Cblock(cpp_util.CloseNamespace(self._namespace)) + ) + (c.Append('#endif // %s' % ifndef_name) + .Append() + ) + return c + + def _GeneratePublicBody(self): + c = Code() + + (c.Append('%s();' % self._class_name) + .Append() + .Append('enum ID {') + .Concat(self._GenerateEnumConstants()) + .Eblock('};') + .Append() + .Append('const char* ToString(ID id) const;') + .Append('ID FromString(const std::string& id) const;') + .Append() + ) + return c + + def _GeneratePrivateBody(self): + return Code().Append('std::map<std::string, ' + '%s::ID> features_;' % self._class_name) + + def _GenerateEnumConstants(self): + c = Code() + + (c.Sblock() + .Append('kUnknown,') + ) + for feature in self._feature_defs: + c.Append('%s,' % cpp_util.ConstantName(feature.name)) + c.Append('kEnumBoundary') + + return c diff --git a/chromium/tools/json_schema_compiler/idl_schema.py b/chromium/tools/json_schema_compiler/idl_schema.py index 44a4a55eaec..c6e49bf507b 100644 --- a/chromium/tools/json_schema_compiler/idl_schema.py +++ b/chromium/tools/json_schema_compiler/idl_schema.py @@ -100,14 +100,14 @@ class Callspec(object): return_type = None if self.node.GetProperty('TYPEREF') not in ('void', None): return_type = Typeref(self.node.GetProperty('TYPEREF'), - self.node, + self.node.parent, {'name': self.node.GetName()}).process(callbacks) # The IDL parser doesn't allow specifying return types as optional. # Instead we infer any object return values to be optional. # TODO(asargent): fix the IDL parser to support optional return types. if return_type.get('type') == 'object' or '$ref' in return_type: return_type['optional'] = True - for node in self.node.children: + for node in self.node.GetChildren(): parameter = Param(node).process(callbacks) if parameter['name'] in self.comment: parameter['description'] = self.comment[parameter['name']] @@ -139,7 +139,7 @@ class Dictionary(object): def process(self, callbacks): properties = OrderedDict() - for node in self.node.children: + for node in self.node.GetChildren(): if node.cls == 'Member': k, v = Member(node).process(callbacks) properties[k] = v @@ -181,7 +181,7 @@ class Member(object): option_name)) is_function = False parameter_comments = OrderedDict() - for node in self.node.children: + for node in self.node.GetChildren(): if node.cls == 'Comment': (parent_comment, parameter_comments) = ProcessComment(node.GetName()) properties['description'] = parent_comment @@ -223,7 +223,7 @@ class Typeref(object): properties = self.additional_properties result = properties - if self.parent.GetProperty('OPTIONAL', False): + if self.parent.GetProperty('OPTIONAL'): properties['optional'] = True # The IDL parser denotes array types by adding a child 'Array' node onto @@ -290,9 +290,15 @@ class Enum(object): def process(self, callbacks): enum = [] - for node in self.node.children: + for node in self.node.GetChildren(): if node.cls == 'EnumItem': - enum.append(node.GetName()) + enum_value = {'name': node.GetName()} + for child in node.GetChildren(): + if child.cls == 'Comment': + enum_value['description'] = ProcessComment(child.GetName())[0] + else: + raise ValueError('Did not process %s %s' % (child.cls, child)) + enum.append(enum_value) elif node.cls == 'Comment': self.description = ProcessComment(node.GetName())[0] else: @@ -313,10 +319,18 @@ class Namespace(object): dictionary that the JSON schema compiler expects to see. ''' - def __init__(self, namespace_node, description, nodoc=False, internal=False): + def __init__(self, + namespace_node, + description, + nodoc=False, + internal=False, + platforms=None, + compiler_options=None): self.namespace = namespace_node self.nodoc = nodoc self.internal = internal + self.platforms = platforms + self.compiler_options = compiler_options self.events = [] self.functions = [] self.types = [] @@ -324,7 +338,7 @@ class Namespace(object): self.description = description def process(self): - for node in self.namespace.children: + for node in self.namespace.GetChildren(): if node.cls == 'Dictionary': self.types.append(Dictionary(node).process(self.callbacks)) elif node.cls == 'Callback': @@ -338,17 +352,23 @@ class Namespace(object): self.types.append(Enum(node).process(self.callbacks)) else: sys.exit('Did not process %s %s' % (node.cls, node)) + if self.compiler_options is not None: + compiler_options = self.compiler_options + else: + compiler_options = {} return {'namespace': self.namespace.GetName(), 'description': self.description, 'nodoc': self.nodoc, 'types': self.types, 'functions': self.functions, 'internal': self.internal, - 'events': self.events} + 'events': self.events, + 'platforms': self.platforms, + 'compiler_options': compiler_options} def process_interface(self, node): members = [] - for member in node.children: + for member in node.GetChildren(): if member.cls == 'Member': name, properties = Member(member).process(self.callbacks) members.append(properties) @@ -369,6 +389,8 @@ class IDLSchema(object): nodoc = False internal = False description = None + platforms = None + compiler_options = None for node in self.idl: if node.cls == 'Namespace': if not description: @@ -376,10 +398,14 @@ class IDLSchema(object): print('%s must have a namespace-level comment. This will ' 'appear on the API summary page.' % node.GetName()) description = '' - namespace = Namespace(node, description, nodoc, internal) + namespace = Namespace(node, description, nodoc, internal, + platforms=platforms, + compiler_options=compiler_options) namespaces.append(namespace.process()) nodoc = False internal = False + platforms = None + compiler_options = None elif node.cls == 'Copyright': continue elif node.cls == 'Comment': @@ -389,6 +415,10 @@ class IDLSchema(object): nodoc = bool(node.value) elif node.name == 'internal': internal = bool(node.value) + elif node.name == 'platforms': + platforms = list(node.value) + elif node.name == 'implemented_in': + compiler_options = {'implemented_in': node.value} else: continue else: diff --git a/chromium/tools/json_schema_compiler/idl_schema_test.py b/chromium/tools/json_schema_compiler/idl_schema_test.py index 1f5d7d67ec2..212efdc6713 100755 --- a/chromium/tools/json_schema_compiler/idl_schema_test.py +++ b/chromium/tools/json_schema_compiler/idl_schema_test.py @@ -18,6 +18,11 @@ def getParams(schema, name): return function['parameters'] +def getReturns(schema, name): + function = getFunction(schema, name) + return function['returns'] + + def getType(schema, id): for item in schema['types']: if item['id'] == id: @@ -77,7 +82,9 @@ class IdlSchemaTest(unittest.TestCase): def testEnum(self): schema = self.idl_basics - expected = {'enum': ['name1', 'name2'], 'description': 'Enum description', + expected = {'enum': [{'name': 'name1', 'description': 'comment1'}, + {'name': 'name2'}], + 'description': 'Enum description', 'type': 'string', 'id': 'EnumType'} self.assertEquals(expected, getType(schema, expected['id'])) @@ -108,6 +115,60 @@ class IdlSchemaTest(unittest.TestCase): self.assertTrue(idl_basics['internal']) self.assertFalse(idl_basics['nodoc']) + def testReturnTypes(self): + schema = self.idl_basics + self.assertEquals({'name': 'function19', 'type': 'integer'}, + getReturns(schema, 'function19')) + self.assertEquals({'name': 'function20', '$ref': 'MyType1', + 'optional': True}, + getReturns(schema, 'function20')) + self.assertEquals({'name': 'function21', 'type': 'array', + 'items': {'$ref': 'MyType1'}}, + getReturns(schema, 'function21')) + self.assertEquals({'name': 'function22', '$ref': 'EnumType', + 'optional': True}, + getReturns(schema, 'function22')) + self.assertEquals({'name': 'function23', 'type': 'array', + 'items': {'$ref': 'EnumType'}}, + getReturns(schema, 'function23')) + + def testChromeOSPlatformsNamespace(self): + schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0] + self.assertEquals('idl_namespace_chromeos', schema['namespace']) + expected = ['chromeos'] + self.assertEquals(expected, schema['platforms']) + + def testAllPlatformsNamespace(self): + schema = idl_schema.Load('test/idl_namespace_all_platforms.idl')[0] + self.assertEquals('idl_namespace_all_platforms', schema['namespace']) + expected = ['chromeos', 'chromeos_touch', 'linux', 'mac', 'win'] + self.assertEquals(expected, schema['platforms']) + + def testNonSpecificPlatformsNamespace(self): + schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0] + self.assertEquals('idl_namespace_non_specific_platforms', + schema['namespace']) + expected = None + self.assertEquals(expected, schema['platforms']) + + def testSpecificImplementNamespace(self): + schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0] + self.assertEquals('idl_namespace_specific_implement', + schema['namespace']) + expected = 'idl_namespace_specific_implement.idl' + self.assertEquals(expected, schema['compiler_options']['implemented_in']) + + def testSpecificImplementOnChromeOSNamespace(self): + schema = idl_schema.Load( + 'test/idl_namespace_specific_implement_chromeos.idl')[0] + self.assertEquals('idl_namespace_specific_implement_chromeos', + schema['namespace']) + expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl' + expected_platform = ['chromeos'] + self.assertEquals(expected_implemented_path, + schema['compiler_options']['implemented_in']) + self.assertEquals(expected_platform, schema['platforms']) + def testCallbackComment(self): schema = self.idl_basics self.assertEquals('A comment on a callback.', @@ -143,10 +204,12 @@ class IdlSchemaTest(unittest.TestCase): schema = idl_schema.Load('test/idl_reserved_words.idl')[0] foo_type = getType(schema, 'Foo') - self.assertEquals(['float', 'DOMString'], foo_type['enum']) + self.assertEquals([{'name': 'float'}, {'name': 'DOMString'}], + foo_type['enum']) enum_type = getType(schema, 'enum') - self.assertEquals(['callback', 'namespace'], enum_type['enum']) + self.assertEquals([{'name': 'callback'}, {'name': 'namespace'}], + enum_type['enum']) dictionary = getType(schema, 'dictionary') self.assertEquals('integer', dictionary['properties']['long']['type']) diff --git a/chromium/tools/json_schema_compiler/memoize.py b/chromium/tools/json_schema_compiler/memoize.py index 1402a6ecd80..228e7e3f82d 100644 --- a/chromium/tools/json_schema_compiler/memoize.py +++ b/chromium/tools/json_schema_compiler/memoize.py @@ -6,8 +6,9 @@ def memoize(fn): '''Decorates |fn| to memoize. ''' memory = {} - def impl(*args): - if args not in memory: - memory[args] = fn(*args) - return memory[args] + def impl(*args, **optargs): + full_args = args + tuple(optargs.iteritems()) + if full_args not in memory: + memory[full_args] = fn(*args, **optargs) + return memory[full_args] return impl diff --git a/chromium/tools/json_schema_compiler/model.py b/chromium/tools/json_schema_compiler/model.py index 082fbc60f85..dd68e9b1056 100644 --- a/chromium/tools/json_schema_compiler/model.py +++ b/chromium/tools/json_schema_compiler/model.py @@ -7,6 +7,7 @@ import os.path from json_parse import OrderedDict from memoize import memoize + class ParseException(Exception): """Thrown when data in the model is invalid. """ @@ -16,6 +17,7 @@ class ParseException(Exception): Exception.__init__( self, 'Model parse exception at:\n' + '\n'.join(hierarchy)) + class Model(object): """Model of all namespaces that comprise an API. @@ -34,6 +36,45 @@ class Model(object): self.namespaces[namespace.name] = namespace return namespace + +def CreateFeature(name, model): + if isinstance(model, dict): + return SimpleFeature(name, model) + return ComplexFeature(name, [SimpleFeature(name, child) for child in model]) + + +class ComplexFeature(object): + """A complex feature which may be made of several simple features. + + Properties: + - |name| the name of the feature + - |unix_name| the unix_name of the feature + - |feature_list| a list of simple features which make up the feature + """ + def __init__(self, feature_name, features): + self.name = feature_name + self.unix_name = UnixName(self.name) + self.feature_list = features + +class SimpleFeature(object): + """A simple feature, which can make up a complex feature, as specified in + files such as chrome/common/extensions/api/_permission_features.json. + + Properties: + - |name| the name of the feature + - |unix_name| the unix_name of the feature + - |channel| the channel where the feature is released + - |extension_types| the types which can use the feature + - |whitelist| a list of extensions allowed to use the feature + """ + def __init__(self, feature_name, feature_def): + self.name = feature_name + self.unix_name = UnixName(self.name) + self.channel = feature_def['channel'] + self.extension_types = feature_def['extension_types'] + self.whitelist = feature_def.get('whitelist') + + class Namespace(object): """An API namespace. @@ -71,8 +112,12 @@ class Namespace(object): self.functions = _GetFunctions(self, json, self) self.events = _GetEvents(self, json, self) self.properties = _GetProperties(self, json, self, toplevel_origin) - self.compiler_options = (json.get('compiler_options', {}) - if include_compiler_options else {}) + if include_compiler_options: + self.compiler_options = json.get('compiler_options', {}) + else: + self.compiler_options = {} + self.documentation_options = json.get('documentation_options', {}) + class Origin(object): """Stores the possible origin of model object as a pair of bools. These are: @@ -92,6 +137,7 @@ class Origin(object): self.from_client = from_client self.from_json = from_json + class Type(object): """A Type defined in the json. @@ -140,7 +186,7 @@ class Type(object): self.ref_type = json['$ref'] elif 'enum' in json and json_type == 'string': self.property_type = PropertyType.ENUM - self.enum_values = [value for value in json['enum']] + self.enum_values = [EnumValue(value) for value in json['enum']] elif json_type == 'any': self.property_type = PropertyType.ANY elif json_type == 'binary': @@ -198,6 +244,7 @@ class Type(object): else: raise ParseException(self, 'Unsupported JSON type %s' % json_type) + class Function(object): """A Function defined in the API. @@ -207,6 +254,7 @@ class Function(object): available to - |params| a list of parameters to the function (order matters). A separate parameter is used for each choice of a 'choices' parameter + - |deprecated| a reason and possible alternative for a deprecated function - |description| a description of the function (if provided) - |callback| the callback parameter to the function. There should be exactly one @@ -227,6 +275,7 @@ class Function(object): self.platforms = _GetPlatforms(json) self.params = [] self.description = json.get('description') + self.deprecated = json.get('deprecated') self.callback = None self.optional = json.get('optional', False) self.parent = parent @@ -236,6 +285,7 @@ class Function(object): self.actions = options.get('actions', []) self.supports_listeners = options.get('supportsListeners', True) self.supports_rules = options.get('supportsRules', False) + self.supports_dom = options.get('supportsDom', False) def GeneratePropertyFromParam(p): return Property(self, p['name'], p, namespace, origin) @@ -268,6 +318,7 @@ class Function(object): namespace, origin) + class Property(object): """A property of a type OR a parameter to a function. Properties: @@ -278,6 +329,7 @@ class Property(object): - |description| a description of the property (if provided) - |type_| the model.Type of this property - |simple_name| the name of this Property without a namespace + - |deprecated| a reason and possible alternative for a deprecated property """ def __init__(self, parent, name, json, namespace, origin): """Creates a Property from JSON. @@ -291,6 +343,7 @@ class Property(object): self.description = json.get('description', None) self.optional = json.get('optional', None) self.instance_of = json.get('isInstanceOf', None) + self.deprecated = json.get('deprecated') # HACK: only support very specific value types. is_allowed_value = ( @@ -339,6 +392,20 @@ class Property(object): unix_name = property(GetUnixName, SetUnixName) +class EnumValue(object): + """A single value from an enum. + Properties: + - |name| name of the property as in the json. + - |description| a description of the property (if provided) + """ + def __init__(self, json): + if isinstance(json, dict): + self.name = json['name'] + self.description = json.get('description') + else: + self.name = json + self.description = None + class _Enum(object): """Superclass for enum types with a "name" field, setting up repr/eq/ne. Enums need to do this so that equality/non-equality work over pickling. @@ -366,11 +433,13 @@ class _Enum(object): def __str__(self): return repr(self) + class _PropertyTypeInfo(_Enum): def __init__(self, is_fundamental, name): _Enum.__init__(self, name) self.is_fundamental = is_fundamental + class PropertyType(object): """Enum of different types of properties/parameters. """ @@ -388,6 +457,7 @@ class PropertyType(object): REF = _PropertyTypeInfo(False, "ref") STRING = _PropertyTypeInfo(True, "string") + @memoize def UnixName(name): '''Returns the unix_style name for a given lowerCamelCase string. @@ -409,11 +479,13 @@ def UnixName(name): unix_name.append(c.lower()) return ''.join(unix_name) + def _StripNamespace(name, namespace): if name.startswith(namespace.name + '.'): return name[len(namespace.name + '.'):] return name + def _GetModelHierarchy(entity): """Returns the hierarchy of the given model entity.""" hierarchy = [] @@ -425,6 +497,7 @@ def _GetModelHierarchy(entity): hierarchy.reverse() return hierarchy + def _GetTypes(parent, json, namespace, origin): """Creates Type objects extracted from |json|. """ @@ -434,6 +507,7 @@ def _GetTypes(parent, json, namespace, origin): types[type_.name] = type_ return types + def _GetFunctions(parent, json, namespace): """Creates Function objects extracted from |json|. """ @@ -447,6 +521,7 @@ def _GetFunctions(parent, json, namespace): functions[function.name] = function return functions + def _GetEvents(parent, json, namespace): """Creates Function objects generated from the events in |json|. """ @@ -460,6 +535,7 @@ def _GetEvents(parent, json, namespace): events[event.name] = event return events + def _GetProperties(parent, json, namespace, origin): """Generates Property objects extracted from |json|. """ @@ -468,10 +544,12 @@ def _GetProperties(parent, json, namespace, origin): properties[name] = Property(parent, name, property_json, namespace, origin) return properties + class _PlatformInfo(_Enum): def __init__(self, name): _Enum.__init__(self, name) + class Platforms(object): """Enum of the possible platforms. """ @@ -481,9 +559,13 @@ class Platforms(object): MAC = _PlatformInfo("mac") WIN = _PlatformInfo("win") + def _GetPlatforms(json): - if 'platforms' not in json: + if 'platforms' not in json or json['platforms'] == None: return None + # Sanity check: platforms should not be an empty list. + if not json['platforms']: + raise ValueError('"platforms" cannot be an empty list') platforms = [] for platform_name in json['platforms']: for platform_enum in _Enum.GetAll(Platforms): diff --git a/chromium/tools/json_schema_compiler/model_test.py b/chromium/tools/json_schema_compiler/model_test.py index bdd20092810..0e398a5a5bb 100755 --- a/chromium/tools/json_schema_compiler/model_test.py +++ b/chromium/tools/json_schema_compiler/model_test.py @@ -4,6 +4,8 @@ # found in the LICENSE file. from json_schema import CachedLoad +from idl_schema import Load +from model import Platforms import model import unittest @@ -22,9 +24,25 @@ class ModelTest(unittest.TestCase): self.model.AddNamespace(self.tabs_json[0], 'path/to/tabs.json') self.tabs = self.model.namespaces.get('tabs') + self.idl_chromeos = Load('test/idl_namespace_chromeos.idl') + self.model.AddNamespace(self.idl_chromeos[0], + 'path/to/idl_namespace_chromeos.idl') + self.idl_namespace_chromeos = self.model.namespaces.get( + 'idl_namespace_chromeos') + self.idl_all_platforms = Load('test/idl_namespace_all_platforms.idl') + self.model.AddNamespace(self.idl_all_platforms[0], + 'path/to/idl_namespace_all_platforms.idl') + self.idl_namespace_all_platforms = self.model.namespaces.get( + 'idl_namespace_all_platforms') + self.idl_non_specific_platforms = Load( + 'test/idl_namespace_non_specific_platforms.idl') + self.model.AddNamespace(self.idl_non_specific_platforms[0], + 'path/to/idl_namespace_non_specific_platforms.idl') + self.idl_namespace_non_specific_platforms = self.model.namespaces.get( + 'idl_namespace_non_specific_platforms') def testNamespaces(self): - self.assertEquals(3, len(self.model.namespaces)) + self.assertEquals(6, len(self.model.namespaces)) self.assertTrue(self.permissions) def testHasFunctions(self): @@ -100,6 +118,15 @@ class ModelTest(unittest.TestCase): for name in expectations: self.assertEquals(expectations[name], model.UnixName(name)) + def testPlatforms(self): + self.assertEqual([Platforms.CHROMEOS], + self.idl_namespace_chromeos.platforms) + self.assertEqual( + [Platforms.CHROMEOS, Platforms.CHROMEOS_TOUCH, Platforms.LINUX, + Platforms.MAC, Platforms.WIN], + self.idl_namespace_all_platforms.platforms) + self.assertEqual(None, + self.idl_namespace_non_specific_platforms.platforms) if __name__ == '__main__': unittest.main() diff --git a/chromium/tools/json_schema_compiler/ppapi_generator.py b/chromium/tools/json_schema_compiler/ppapi_generator.py new file mode 100644 index 00000000000..e47b9540e34 --- /dev/null +++ b/chromium/tools/json_schema_compiler/ppapi_generator.py @@ -0,0 +1,289 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import collections +import datetime +import os.path +import sys + +import code +import cpp_util +import model + +try: + import jinja2 +except ImportError: + sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', + 'third_party')) + import jinja2 + + +class _PpapiGeneratorBase(object): + """A base class for ppapi generators. + + Implementations should set TEMPLATE_NAME to a string containing the name of + the template file without its extension. The template will be rendered with + the following symbols available: + name: A string containing the name of the namespace. + enums: A list of enums within the namespace. + types: A list of types within the namespace, sorted such that no element + depends on an earlier element. + events: A dict of events within the namespace. + functions: A dict of functions within the namespace. + year: An int containing the current year. + source_file: The name of the input file. + """ + + def __init__(self, namespace): + self._namespace = namespace + self._required_types = {} + self._array_types = set() + self._optional_types = set() + self._optional_array_types = set() + self._dependencies = collections.OrderedDict() + self._types = [] + self._enums = [] + + self.jinja_environment = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.join(os.path.dirname(__file__), + 'templates', 'ppapi'))) + self._SetupFilters() + self._ResolveTypeDependencies() + + def _SetupFilters(self): + self.jinja_environment.filters.update({ + 'ppapi_type': self.ToPpapiType, + 'classname': cpp_util.Classname, + 'enum_value': self.EnumValueName, + 'return_type': self.GetFunctionReturnType, + 'format_param_type': self.FormatParamType, + 'needs_optional': self.NeedsOptional, + 'needs_array': self.NeedsArray, + 'needs_optional_array': self.NeedsOptionalArray, + 'has_array_outs': self.HasArrayOuts, + }) + + def Render(self, template_name, values): + generated_code = code.Code() + template = self.jinja_environment.get_template( + '%s.template' % template_name) + generated_code.Append(template.render(values)) + return generated_code + + def Generate(self): + """Generates a Code object for a single namespace.""" + return self.Render(self.TEMPLATE_NAME, { + 'name': self._namespace.name, + 'enums': self._enums, + 'types': self._types, + 'events': self._namespace.events, + 'functions': self._namespace.functions, + # TODO(sammc): Don't change years when regenerating existing output files. + 'year': datetime.date.today().year, + 'source_file': self._namespace.source_file, + }) + + def _ResolveTypeDependencies(self): + """Calculates the transitive closure of the types in _required_types. + + Returns a tuple containing the list of struct types and the list of enum + types. The list of struct types is ordered such that no type depends on a + type later in the list. + + """ + if self._namespace.functions: + for function in self._namespace.functions.itervalues(): + self._FindFunctionDependencies(function) + + if self._namespace.events: + for event in self._namespace.events.itervalues(): + self._FindFunctionDependencies(event) + resolved_types = set() + while resolved_types < set(self._required_types): + for typename in sorted(set(self._required_types) - resolved_types): + type_ = self._required_types[typename] + self._dependencies.setdefault(typename, set()) + for member in type_.properties.itervalues(): + self._RegisterDependency(member, self._NameComponents(type_)) + resolved_types.add(typename) + while self._dependencies: + for name, deps in self._dependencies.items(): + if not deps: + if (self._required_types[name].property_type == + model.PropertyType.ENUM): + self._enums.append(self._required_types[name]) + else: + self._types.append(self._required_types[name]) + for deps in self._dependencies.itervalues(): + deps.discard(name) + del self._dependencies[name] + break + else: + raise ValueError('Circular dependency %s' % self._dependencies) + + def _FindFunctionDependencies(self, function): + for param in function.params: + self._RegisterDependency(param, None) + if function.callback: + for param in function.callback.params: + self._RegisterDependency(param, None) + if function.returns: + self._RegisterTypeDependency(function.returns, None, False, False) + + def _RegisterDependency(self, member, depender): + self._RegisterTypeDependency(member.type_, depender, member.optional, False) + + def _RegisterTypeDependency(self, type_, depender, optional, array): + if type_.property_type == model.PropertyType.ARRAY: + self._RegisterTypeDependency(type_.item_type, depender, optional, True) + elif type_.property_type == model.PropertyType.REF: + self._RegisterTypeDependency(self._namespace.types[type_.ref_type], + depender, optional, array) + elif type_.property_type in (model.PropertyType.OBJECT, + model.PropertyType.ENUM): + name_components = self._NameComponents(type_) + self._required_types[name_components] = type_ + if depender: + self._dependencies.setdefault(depender, set()).add( + name_components) + if array: + self._array_types.add(name_components) + if optional: + self._optional_array_types.add(name_components) + elif optional: + self._optional_types.add(name_components) + + @staticmethod + def _NameComponents(entity): + """Returns a tuple of the fully-qualified name of an entity.""" + names = [] + while entity: + if (not isinstance(entity, model.Type) or + entity.property_type != model.PropertyType.ARRAY): + names.append(entity.name) + entity = entity.parent + return tuple(reversed(names[:-1])) + + def ToPpapiType(self, type_, array=False, optional=False): + """Returns a string containing the name of the Pepper C type for |type_|. + + If array is True, returns the name of an array of |type_|. If optional is + True, returns the name of an optional |type_|. If both array and optional + are True, returns the name of an optional array of |type_|. + """ + if isinstance(type_, model.Function) or type_.property_type in ( + model.PropertyType.OBJECT, model.PropertyType.ENUM): + return self._FormatPpapiTypeName( + array, optional, '_'.join( + cpp_util.Classname(s) for s in self._NameComponents(type_)), + namespace=cpp_util.Classname(self._namespace.name)) + elif type_.property_type == model.PropertyType.REF: + return self.ToPpapiType(self._namespace.types[type_.ref_type], + optional=optional, array=array) + elif type_.property_type == model.PropertyType.ARRAY: + return self.ToPpapiType(type_.item_type, array=True, + optional=optional) + elif type_.property_type == model.PropertyType.STRING and not array: + return 'PP_Var' + elif array or optional: + if type_.property_type in self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP: + return self._FormatPpapiTypeName( + array, optional, + self._PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP[type_.property_type], '') + return self._PPAPI_PRIMITIVE_TYPE_MAP.get(type_.property_type, 'PP_Var') + + _PPAPI_PRIMITIVE_TYPE_MAP = { + model.PropertyType.BOOLEAN: 'PP_Bool', + model.PropertyType.DOUBLE: 'double_t', + model.PropertyType.INT64: 'int64_t', + model.PropertyType.INTEGER: 'int32_t', + } + _PPAPI_COMPOUND_PRIMITIVE_TYPE_MAP = { + model.PropertyType.BOOLEAN: 'Bool', + model.PropertyType.DOUBLE: 'Double', + model.PropertyType.INT64: 'Int64', + model.PropertyType.INTEGER: 'Int32', + model.PropertyType.STRING: 'String', + } + + @staticmethod + def _FormatPpapiTypeName(array, optional, name, namespace=''): + if namespace: + namespace = '%s_' % namespace + if array: + if optional: + return 'PP_%sOptional_%s_Array' % (namespace, name) + return 'PP_%s%s_Array' % (namespace, name) + if optional: + return 'PP_%sOptional_%s' % (namespace, name) + return 'PP_%s%s' % (namespace, name) + + def NeedsOptional(self, type_): + """Returns True if an optional |type_| is required.""" + return self._NameComponents(type_) in self._optional_types + + def NeedsArray(self, type_): + """Returns True if an array of |type_| is required.""" + return self._NameComponents(type_) in self._array_types + + def NeedsOptionalArray(self, type_): + """Returns True if an optional array of |type_| is required.""" + return self._NameComponents(type_) in self._optional_array_types + + def FormatParamType(self, param): + """Formats the type of a parameter or property.""" + return self.ToPpapiType(param.type_, optional=param.optional) + + @staticmethod + def GetFunctionReturnType(function): + return 'int32_t' if function.callback or function.returns else 'void' + + def EnumValueName(self, enum_value, enum_type): + """Returns a string containing the name for an enum value.""" + return '%s_%s' % (self.ToPpapiType(enum_type).upper(), + enum_value.name.upper()) + + def _ResolveType(self, type_): + if type_.property_type == model.PropertyType.REF: + return self._ResolveType(self._namespace.types[type_.ref_type]) + if type_.property_type == model.PropertyType.ARRAY: + return self._ResolveType(type_.item_type) + return type_ + + def _IsOrContainsArray(self, type_): + if type_.property_type == model.PropertyType.ARRAY: + return True + type_ = self._ResolveType(type_) + if type_.property_type == model.PropertyType.OBJECT: + return any(self._IsOrContainsArray(param.type_) + for param in type_.properties.itervalues()) + return False + + def HasArrayOuts(self, function): + """Returns True if the function produces any arrays as outputs. + + This includes arrays that are properties of other objects. + """ + if function.callback: + for param in function.callback.params: + if self._IsOrContainsArray(param.type_): + return True + return function.returns and self._IsOrContainsArray(function.returns) + + +class _IdlGenerator(_PpapiGeneratorBase): + TEMPLATE_NAME = 'idl' + + +class _GeneratorWrapper(object): + def __init__(self, generator_factory): + self._generator_factory = generator_factory + + def Generate(self, namespace): + return self._generator_factory(namespace).Generate() + + +class PpapiGenerator(object): + def __init__(self): + self.idl_generator = _GeneratorWrapper(_IdlGenerator) diff --git a/chromium/tools/json_schema_compiler/ppapi_generator_test.py b/chromium/tools/json_schema_compiler/ppapi_generator_test.py new file mode 100755 index 00000000000..4d396ffa91e --- /dev/null +++ b/chromium/tools/json_schema_compiler/ppapi_generator_test.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import unittest + +from model import Model +import ppapi_generator +from schema_loader import SchemaLoader + + +def _LoadNamespace(filename): + filename = os.path.join(os.path.dirname(__file__), filename) + schema_loader = SchemaLoader( + os.path.dirname(os.path.relpath(os.path.normpath(filename), + os.path.dirname(filename))), + os.path.dirname(filename)) + schema = os.path.normpath(filename) + api_def = schema_loader.LoadSchema(os.path.split(schema)[1])[0] + + api_model = Model() + relpath = os.path.relpath(os.path.normpath(filename), + os.path.dirname(filename)) + namespace = api_model.AddNamespace(api_def, relpath) + generator = ppapi_generator._PpapiGeneratorBase(namespace) + return namespace, generator + + +class PpapiGeneratorIdlStructsTest(unittest.TestCase): + def setUp(self): + self.namespace, self.generator = _LoadNamespace( + os.path.join('test', 'idl_pepper.idl')) + + def testTypesOrder(self): + typename_to_index = dict( + (type_.name, i) for i, type_ in enumerate(self.generator._types)) + self.assertLess(typename_to_index['MyType1'], typename_to_index['MyType2']) + self.assertLess(typename_to_index['MyType1'], typename_to_index['MyType3']) + self.assertLess(typename_to_index['MyType1'], typename_to_index['MyType4']) + self.assertLess(typename_to_index['MyType1'], typename_to_index['MyType5']) + self.assertLess(typename_to_index['MyType3'], typename_to_index['MyType5']) + self.assertLess(typename_to_index['MyType4'], typename_to_index['MyType5']) + self.assertLess(typename_to_index['MyType5'], typename_to_index['MyType6']) + + def testNeedsArray(self): + self.assertFalse(self.generator.NeedsArray(self.namespace.types['MyType1'])) + self.assertTrue(self.generator.NeedsArray(self.namespace.types['MyType2'])) + self.assertTrue(self.generator.NeedsArray(self.namespace.types['MyType3'])) + self.assertFalse(self.generator.NeedsArray(self.namespace.types['MyType4'])) + self.assertFalse(self.generator.NeedsArray(self.namespace.types['MyType5'])) + self.assertTrue(self.generator.NeedsArray(self.namespace.types['EnumType'])) + + def testNeedsOptional(self): + self.assertFalse( + self.generator.NeedsOptional(self.namespace.types['MyType1'])) + self.assertFalse( + self.generator.NeedsOptional(self.namespace.types['MyType2'])) + self.assertTrue( + self.generator.NeedsOptional(self.namespace.types['MyType3'])) + self.assertTrue( + self.generator.NeedsOptional(self.namespace.types['MyType4'])) + self.assertFalse( + self.generator.NeedsOptional(self.namespace.types['MyType5'])) + self.assertTrue( + self.generator.NeedsOptional(self.namespace.types['EnumType'])) + + def testNeedsOptionalArray(self): + self.assertFalse( + self.generator.NeedsOptionalArray(self.namespace.types['MyType1'])) + self.assertTrue( + self.generator.NeedsOptionalArray(self.namespace.types['MyType2'])) + self.assertFalse( + self.generator.NeedsOptionalArray(self.namespace.types['MyType3'])) + self.assertFalse( + self.generator.NeedsOptionalArray(self.namespace.types['MyType4'])) + self.assertFalse( + self.generator.NeedsOptionalArray(self.namespace.types['MyType5'])) + self.assertTrue( + self.generator.NeedsOptionalArray(self.namespace.types['EnumType'])) + + def testFormatParamTypePrimitive(self): + self.assertEqual('int32_t', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['int_single'])) + self.assertEqual('PP_Int32_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['int_array'])) + self.assertEqual('PP_Optional_Int32', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_int'])) + self.assertEqual('PP_Optional_Int32_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_int_array'])) + self.assertEqual('double_t', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['double_single'])) + self.assertEqual('PP_Double_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['double_array'])) + self.assertEqual('PP_Optional_Double', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_double'])) + self.assertEqual('PP_Optional_Double_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_double_array'])) + self.assertEqual('PP_Var', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['string'])) + self.assertEqual('PP_String_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['string_array'])) + self.assertEqual('PP_Var', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_string'])) + self.assertEqual('PP_Optional_String_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_string_array'])) + + def testFormatParamTypeStruct(self): + self.assertEqual('PP_IdlPepper_MyType0', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['struct_single'])) + self.assertEqual( + 'PP_IdlPepper_MyType0_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['struct_array'])) + self.assertEqual( + 'PP_IdlPepper_Optional_MyType0', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_struct'])) + self.assertEqual( + 'PP_IdlPepper_Optional_MyType0_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties[ + 'optional_struct_array'])) + + def testFormatParamTypeEnum(self): + self.assertEqual('PP_IdlPepper_EnumType', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['enum_single'])) + self.assertEqual( + 'PP_IdlPepper_EnumType_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['enum_array'])) + self.assertEqual( + 'PP_IdlPepper_Optional_EnumType', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_enum'])) + self.assertEqual( + 'PP_IdlPepper_Optional_EnumType_Array', self.generator.FormatParamType( + self.namespace.types['MyType'].properties['optional_enum_array'])) + + def testEnumValueName(self): + enum_type = self.namespace.types['EnumType'] + self.assertEqual('PP_IDLPEPPER_ENUMTYPE_NAME1', + self.generator.EnumValueName(enum_type.enum_values[0], + enum_type)) + self.assertEqual('PP_IDLPEPPER_ENUMTYPE_NAME2', + self.generator.EnumValueName(enum_type.enum_values[1], + enum_type)) + enum_type = self.namespace.types['AnotherEnumType'] + self.assertEqual('PP_IDLPEPPER_ANOTHERENUMTYPE_NAME1', + self.generator.EnumValueName(enum_type.enum_values[0], + enum_type)) + self.assertEqual('PP_IDLPEPPER_ANOTHERENUMTYPE_NAME2', + self.generator.EnumValueName(enum_type.enum_values[1], + enum_type)) + + def testReturnTypes(self): + self.assertEqual('void', self.generator.GetFunctionReturnType( + self.namespace.functions['function1'])) + self.assertEqual('void', self.generator.GetFunctionReturnType( + self.namespace.functions['function2'])) + self.assertEqual('int32_t', self.generator.GetFunctionReturnType( + self.namespace.functions['function3'])) + self.assertEqual('int32_t', self.generator.GetFunctionReturnType( + self.namespace.functions['function4'])) + self.assertEqual('int32_t', self.generator.GetFunctionReturnType( + self.namespace.functions['function5'])) + self.assertEqual('int32_t', self.generator.GetFunctionReturnType( + self.namespace.functions['function6'])) + + def testHasOutArray(self): + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function1'])) + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function2'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function3'])) + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function4'])) + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function5'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function6'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function7'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function8'])) + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function9'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function10'])) + self.assertTrue(self.generator.HasArrayOuts( + self.namespace.functions['function11'])) + self.assertFalse(self.generator.HasArrayOuts( + self.namespace.functions['function12'])) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/schema_loader.py b/chromium/tools/json_schema_compiler/schema_loader.py index c434dc16746..9fea1ab8250 100644 --- a/chromium/tools/json_schema_compiler/schema_loader.py +++ b/chromium/tools/json_schema_compiler/schema_loader.py @@ -11,9 +11,16 @@ from model import Model class SchemaLoader(object): '''Resolves a type name into the namespace the type belongs to. + + Properties: + - |display_path| path to the directory with the API header files, intended for + use with the Model. + - |real_path| path to the directory with the API header files, used for file + access. ''' - def __init__(self, api_path): - self._api_path = api_path + def __init__(self, display_path, real_path): + self._display_path = display_path + self._real_path = real_path def ResolveType(self, full_name, default_namespace): name_parts = full_name.rsplit('.', 1) @@ -25,29 +32,32 @@ class SchemaLoader(object): real_name = None for ext in ['json', 'idl']: filename = '%s.%s' % (namespace_name, ext) - if os.path.exists(filename): + filepath = os.path.join(self._real_path, filename); + if os.path.exists(filepath): real_name = filename break if real_name is None: return None - namespace = Model().AddNamespace(self.LoadSchema(real_name)[0], - os.path.join(self._api_path, real_name)) + namespace = Model().AddNamespace( + self.LoadSchema(real_name)[0], + os.path.join(self._display_path, real_name)) if type_name not in namespace.types: return None return namespace def LoadSchema(self, schema): + '''Load a schema definition. The schema parameter must be a file name + without any path component - the file is loaded from the path defined by + the real_path argument passed to the constructor.''' schema_filename, schema_extension = os.path.splitext(schema) + schema_path = os.path.join(self._real_path, schema); if schema_extension == '.json': - api_defs = json_schema.Load(schema) + api_defs = json_schema.Load(schema_path) elif schema_extension == '.idl': - api_defs = idl_schema.Load(schema) + api_defs = idl_schema.Load(schema_path) else: sys.exit('Did not recognize file extension %s for schema %s' % (schema_extension, schema)) - if len(api_defs) != 1: - sys.exit('File %s has multiple schemas. Files are only allowed to contain' - 'a single schema.' % schema) return api_defs diff --git a/chromium/tools/json_schema_compiler/templates/ppapi/base.template b/chromium/tools/json_schema_compiler/templates/ppapi/base.template new file mode 100644 index 00000000000..df1aa8ef889 --- /dev/null +++ b/chromium/tools/json_schema_compiler/templates/ppapi/base.template @@ -0,0 +1,14 @@ +{# Copyright 2013 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. -#} + +// Copyright {{ year }} The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// GENERATED FROM THE API DEFINITION IN +// {{ source_file }} +// DO NOT EDIT. + +{% block content %} +{% endblock %} diff --git a/chromium/tools/json_schema_compiler/templates/ppapi/idl.template b/chromium/tools/json_schema_compiler/templates/ppapi/idl.template new file mode 100644 index 00000000000..9c5b998f97f --- /dev/null +++ b/chromium/tools/json_schema_compiler/templates/ppapi/idl.template @@ -0,0 +1,101 @@ +{# Copyright 2013 The Chromium Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. -#} + +{% extends "base.template" %} + +{% macro optional_array_struct(type) %} +{%- if type | needs_optional_array %} +struct {{ type | ppapi_type(array=True, optional=True) }} { + {{ type | ppapi_type(array=True) }} value; + PP_Bool is_set; +}; +{% endif -%} +{% endmacro %} + +{% macro array_struct(type) %} +{%- if type | needs_array %} +struct {{ type | ppapi_type(array=True) }} { + uint32_t size; + [size_is(size)] {{ type | ppapi_type }}[] elements; +}; +{% endif -%} +{% endmacro %} + +{% macro optional_struct(type) %} +{%- if type | needs_optional %} +struct {{ type | ppapi_type(optional=True) }} { + {{ type | ppapi_type }} value; + PP_Bool is_set; +}; +{% endif -%} +{% endmacro %} + +{% block content -%} +{# TODO(sammc): Generate version information. -#} +label Chrome { + [channel=dev] M33 = 0.1 +}; +{% for type in enums %} +enum {{ type | ppapi_type }} { + {%- for value in type.enum_values %} + {{ value | enum_value(type) }}{% if not loop.last %},{% endif %} + {%- endfor %} +}; +{{ optional_struct(type) -}} +{{ array_struct(type) -}} +{{ optional_array_struct(type) -}} +{%- endfor %} +{%- for type in types %} +struct {{ type | ppapi_type }} { + {%- for member in type.properties.itervalues() %} + {{ member | format_param_type }} {{ member.unix_name}}; + {%- endfor %} +}; +{{ optional_struct(type) -}} +{{ array_struct(type) -}} +{{ optional_array_struct(type) -}} +{% endfor %} +{%- for event in events.itervalues() %} +typedef void {{ event | ppapi_type }}( + [in] uint32_t listener_id, + [inout] mem_t user_data{% if event.params %},{% endif %} + {%- for param in event.params %} + [in] {{ param | format_param_type }} {{ param.unix_name }} + {%- if not loop.last %},{% endif %} + {%- endfor -%} +); +{% endfor %} +interface PPB_{{ name | classname }} { +{% for function in functions.itervalues() %} + {{ function | return_type }} {{ function.name | classname }}( + [in] PP_Instance instance + {%- if function.params or function.callback or function.returns %}, + {%- endif %} + {%- for param in function.params %} + [in] {{ param | format_param_type }} {{ param.unix_name }} + {%- if not loop.last or function.callback or function.returns %}, + {%- endif %} + {%- endfor -%} + {%- if function.returns %} + [out] {{ function.returns | ppapi_type }} result, + {%- endif %} + {%- for param in function.callback.params %} + [out] {{ param | format_param_type }} {{ param.unix_name }}, + {%- endfor %} + {%- if function.callback or function.returns %} + {%- if function | has_array_outs %} + [in] PP_ArrayOutput array_allocator, + {%- endif %} + [in] PP_CompletionCallback callback + {%- endif -%} + ); +{% endfor -%} +{% for event in events.itervalues() %} + uint32_t Add{{ event.name | classname }}Listener ( + [in] PP_Instance instance, + [in] {{ event | ppapi_type }} callback, + [inout] mem_t user_data); +{% endfor %} +}; +{% endblock %} |