| # Copyright 2014 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Generate Cobalt bindings (.h and .cpp files). |
| |
| Based on and borrows heavily from code_generator_v8.py which is used as part |
| of the bindings generation pipeline in Blink. |
| """ |
| |
| import abc |
| from datetime import date |
| import os |
| import sys |
| |
| # Add blink's binding scripts to the path, so we can import |
| module_path, module_filename = os.path.split(os.path.realpath(__file__)) |
| blink_script_dir = os.path.normpath(os.path.join( |
| module_path, os.pardir, os.pardir, 'third_party', 'blink', 'Source')) |
| sys.path.append(blink_script_dir) |
| |
| from bindings.scripts.code_generator_v8 import CodeGeneratorBase # pylint: disable=g-import-not-at-top |
| from bindings.scripts.idl_types import IdlType |
| from bindings.scripts.idl_types import inherits_interface |
| import contexts |
| import jinja2 |
| from name_conversion import convert_to_cobalt_name |
| from name_conversion import get_interface_name |
| |
| module_path, module_filename = os.path.split(os.path.realpath(__file__)) |
| |
| # Track the cobalt directory, so we can use it for building relative paths. |
| cobalt_dir = os.path.normpath(os.path.join(module_path, os.pardir)) |
| |
| SHARED_TEMPLATES_DIR = os.path.abspath(os.path.join(module_path, 'templates')) |
| |
| |
| def initialize_jinja_env(cache_dir, templates_dir): |
| """Initialize the Jinja2 environment.""" |
| assert os.path.isabs(templates_dir) |
| assert os.path.isabs(SHARED_TEMPLATES_DIR) |
| jinja_env = jinja2.Environment( |
| loader=jinja2.FileSystemLoader([templates_dir, SHARED_TEMPLATES_DIR]), |
| extensions=['jinja2.ext.do'], # do statement |
| # Bytecode cache is not concurrency-safe unless pre-cached: |
| # if pre-cached this is read-only, but writing creates a race condition. |
| bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
| keep_trailing_newline=True, # newline-terminate generated files |
| lstrip_blocks=True, # so can indent control flow tags |
| trim_blocks=True) |
| return jinja_env |
| |
| |
| def normalize_slashes(path): |
| if os.path.sep == '\\': |
| return path.replace('\\', '/') |
| else: |
| return path |
| |
| |
| def get_implementation_header_path(interface_info): |
| """Get the path to the implementation header file. |
| |
| The interface file must be under cobalt/... |
| |
| Args: |
| interface_info: (dict) Contains the information about an interface |
| defined in an IDL, as generated by blink's |
| compute_interfaces_info_individual.py script |
| Returns: |
| (str) The path to the corresponding implementation header file, starting |
| with the 'cobalt' directory. |
| """ |
| implementation_header_absdir, filename = os.path.split(interface_info[ |
| 'full_path']) |
| |
| implementation_header_reldir = os.path.relpath(implementation_header_absdir, |
| cobalt_dir) |
| assert not os.path.isabs(implementation_header_reldir) |
| assert os.pardir not in implementation_header_reldir.split(os.sep) |
| |
| root, _ = os.path.splitext(filename) |
| header_filename = convert_to_cobalt_name(root) + '.h' |
| return normalize_slashes(os.path.join('cobalt', implementation_header_reldir, |
| header_filename)) |
| |
| |
| def is_global_interface(interface): |
| return (('PrimaryGlobal' in interface.extended_attributes) or |
| ('Global' in interface.extended_attributes)) |
| |
| |
| def get_indexed_special_operation(interface, special): |
| special_operations = list( |
| operation for operation in interface.operations |
| if (special in operation.specials and operation.arguments and str( |
| operation.arguments[0].idl_type) == 'unsigned long')) |
| assert len(special_operations) <= 1, ( |
| 'Multiple indexed %ss defined on interface: %s' % |
| (special, interface.name)) |
| return special_operations[0] if len(special_operations) else None |
| |
| |
| def get_indexed_property_getter(interface): |
| getter_operation = get_indexed_special_operation(interface, 'getter') |
| assert not getter_operation or len(getter_operation.arguments) == 1 |
| return getter_operation |
| |
| |
| def get_indexed_property_setter(interface): |
| setter_operation = get_indexed_special_operation(interface, 'setter') |
| assert not setter_operation or len(setter_operation.arguments) == 2 |
| return setter_operation |
| |
| |
| def get_named_special_operation(interface, special): |
| special_operations = list( |
| operation for operation in interface.operations |
| if (special in operation.specials and operation.arguments and str( |
| operation.arguments[0].idl_type) == 'DOMString')) |
| assert len(special_operations) <= 1, ( |
| 'Multiple named %ss defined on interface: %s' % (special, interface.name)) |
| return special_operations[0] if len(special_operations) else None |
| |
| |
| def get_named_property_getter(interface): |
| getter_operation = get_named_special_operation(interface, 'getter') |
| assert not getter_operation or len(getter_operation.arguments) == 1 |
| return getter_operation |
| |
| |
| def get_named_property_setter(interface): |
| setter_operation = get_named_special_operation(interface, 'setter') |
| assert not setter_operation or len(setter_operation.arguments) == 2 |
| return setter_operation |
| |
| |
| def get_named_property_deleter(interface): |
| deleter_operation = get_named_special_operation(interface, 'deleter') |
| assert not deleter_operation or len(deleter_operation.arguments) == 1 |
| return deleter_operation |
| |
| |
| def get_interface_type_names_from_idl_types(idl_type_list): |
| for idl_type in idl_type_list: |
| if idl_type: |
| if idl_type.is_interface_type: |
| yield get_interface_name(idl_type) |
| elif idl_type.is_union_type: |
| for interface_name in get_interface_type_names_from_idl_types( |
| idl_type.member_types): |
| yield interface_name |
| |
| |
| def get_interface_type_names_from_typed_objects(typed_object_list): |
| for typed_object in typed_object_list: |
| for interface_name in get_interface_type_names_from_idl_types( |
| [typed_object.idl_type]): |
| yield interface_name |
| |
| if hasattr(typed_object, 'arguments'): |
| for interface_name in get_interface_type_names_from_typed_objects( |
| typed_object.arguments): |
| yield interface_name |
| |
| |
| def split_unsupported_properties(context_list): |
| supported = [] |
| unsupported = [] |
| for context in context_list: |
| if context['unsupported']: |
| unsupported.append(context['idl_name']) |
| else: |
| supported.append(context) |
| return supported, unsupported |
| |
| |
| class CodeGeneratorCobalt(CodeGeneratorBase): |
| """Abstract Base code generator class for Cobalt. |
| |
| Concrete classes will provide an implementation for generating bindings for a |
| specific JavaScript engine implementation. |
| """ |
| __metaclass__ = abc.ABCMeta |
| |
| def __init__(self, interfaces_info, templates_dir, cache_dir, output_dir): |
| CodeGeneratorBase.__init__(self, interfaces_info, cache_dir, output_dir) |
| # CodeGeneratorBase inititalizes this with the v8 template path, so |
| # reinitialize it with cobalt's template path |
| |
| # Whether the path is absolute or relative affects the cache file name. Use |
| # the absolute path to ensure that we use the same path as was used when the |
| # cache was prepopulated. |
| self.jinja_env = initialize_jinja_env(cache_dir, |
| os.path.abspath(templates_dir)) |
| |
| @abc.abstractproperty |
| def generated_file_prefix(self): |
| """The prefix to prepend to all generated source files.""" |
| pass |
| |
| @abc.abstractproperty |
| def expression_generator(self): |
| """An instance that implements the ExpressionGenerator class.""" |
| pass |
| |
| def wrapper_class_from_interface_name(self, interface_name): |
| return self.generated_file_prefix + interface_name |
| |
| def wrapper_header_from_interface_name(self, interface_name): |
| attribute_wrapper_class = self.wrapper_class_from_interface_name( |
| interface_name) |
| return normalize_slashes(attribute_wrapper_class + '.h') |
| |
| def output_paths(self, class_name): |
| """Construct the filenames for a generated source file. |
| |
| Construct the header and cc source file paths based on the |
| name of the generated class. |
| |
| Args: |
| class_name: A string that is the name of the interface as defined |
| in the IDL |
| |
| Returns: |
| (str, str): A tuple of two strings representing the full path of |
| the (.h, .cpp) file that will be generated for this interface |
| """ |
| |
| header_path = os.path.join(self.output_dir, class_name + '.h') |
| cc_path = os.path.join(self.output_dir, class_name + '.cc') |
| return header_path, cc_path |
| |
| def render_template(self, header_template_filename, cc_template_filename, |
| template_context): |
| header_template = self.jinja_env.get_template(header_template_filename) |
| cc_template = self.jinja_env.get_template(cc_template_filename) |
| # Make sure extension is .py, not .pyc or .pyo, so doesn't depend on caching |
| module_path_pyname = os.path.join( |
| module_path, os.path.splitext(module_filename)[0] + '.py') |
| # Ensure that posix forward slashes are used |
| template_context['code_generator'] = normalize_slashes(os.path.relpath( |
| module_path_pyname, cobalt_dir)) |
| template_context['header_template_path'] = normalize_slashes( |
| os.path.relpath(header_template.filename, cobalt_dir)) |
| template_context['cc_template_path'] = normalize_slashes(os.path.relpath( |
| cc_template.filename, cobalt_dir)) |
| header_text = header_template.render(template_context) |
| cc_text = cc_template.render(template_context) |
| return header_text, cc_text |
| |
| def generate_code_internal(self, definitions, definition_name): |
| if definition_name in definitions.interfaces: |
| return self.generate_interface_code( |
| definitions, definition_name, definitions.interfaces[definition_name]) |
| if definition_name in definitions.dictionaries: |
| return self.generate_dictionary_code( |
| definitions, definition_name, |
| definitions.dictionaries[definition_name]) |
| raise ValueError('%s is not in IDL definitions' % definition_name) |
| |
| def generate_interface_code(self, definitions, interface_name, interface): |
| interface_info = self.interfaces_info[interface_name] |
| # Select appropriate Jinja template and contents function |
| if interface.is_callback: |
| header_template_filename = 'callback-interface.h.template' |
| cpp_template_filename = 'callback-interface.cc.template' |
| elif interface.is_partial: |
| raise NotImplementedError('Partial interfaces not implemented') |
| else: |
| header_template_filename = 'interface.h.template' |
| cpp_template_filename = 'interface.cc.template' |
| |
| template_context = self.build_interface_context(interface, interface_info, |
| definitions) |
| header_text, cc_text = self.render_template(header_template_filename, |
| cpp_template_filename, |
| template_context) |
| header_path, cc_path = self.output_paths(template_context['binding_class']) |
| return ((header_path, header_text), |
| (cc_path, cc_text),) |
| |
| def generate_dictionary_code(self, definitions, dictionary_name, dictionary): |
| raise NotImplementedError('Dictionaries not implemented') |
| |
| def get_interface_components(self, idl_type_name): |
| """Get the interface's namespace as a list of namespace components.""" |
| idl_filename = self.interfaces_info[idl_type_name].get('full_path') |
| # Get the IDL filename relative to the cobalt directory, and split the |
| # directory to get the list of namespace components. |
| idl_filename = os.path.relpath(idl_filename, cobalt_dir) |
| components = os.path.dirname(idl_filename).split(os.sep) |
| return components |
| |
| def referenced_interface_contexts(self, interface_name): |
| """Yields information for including and declaring this interface's classes. |
| |
| Args: |
| interface_name: The name of the interface. |
| Yields: |
| dict with the following keys: |
| fully_qualified_name: Fully qualified name of the class. |
| include: Path to the header that defines the class. |
| conditional: Symbol on which this interface is conditional compiled. |
| is_callback_interface: True iff this is a callback interface. |
| """ |
| |
| conditional = self.interfaces_info[interface_name]['conditional'] |
| namespace = '::'.join(self.get_interface_components(interface_name)) |
| full_name = 'cobalt::%s::%s' % (namespace, interface_name) |
| |
| # Information about the Cobalt implementation class. |
| yield { |
| 'fully_qualified_name': full_name, |
| 'include': get_implementation_header_path(self.interfaces_info[ |
| interface_name]), |
| 'conditional': conditional, |
| 'is_callback_interface': interface_name in IdlType.callback_interfaces, |
| } |
| yield { |
| 'fully_qualified_name': 'cobalt::%s::%s' % ( |
| namespace, self.wrapper_class_from_interface_name(interface_name)), |
| 'include': self.wrapper_header_from_interface_name(interface_name), |
| 'conditional': conditional, |
| 'is_callback_interface': interface_name in IdlType.callback_interfaces, |
| } |
| |
| def build_interface_context(self, interface, interface_info, definitions): |
| wrapper_class_name = self.wrapper_class_from_interface_name(interface.name) |
| header, _ = self.output_paths(wrapper_class_name) |
| generated_header_relative_path = os.path.relpath(header, self.output_dir) |
| assert not os.path.isabs(generated_header_relative_path) |
| assert os.pardir not in generated_header_relative_path.split(os.sep) |
| |
| interface_components = ( |
| self.get_interface_components(interface.idl_type.name)) |
| context = { |
| # Parameters used for template rendering. |
| 'today': date.today(), |
| 'binding_class': wrapper_class_name, |
| 'fully_qualified_binding_class': ( |
| '::'.join(interface_components + [wrapper_class_name])), |
| 'header_file': normalize_slashes(generated_header_relative_path), |
| 'impl_class': interface.name, |
| 'fully_qualified_impl_class': ( |
| '::'.join(interface_components + [interface.name])), |
| 'interface_name': interface.name, |
| 'is_event_interface': inherits_interface(interface.name, 'Event'), |
| 'is_global_interface': is_global_interface(interface), |
| 'has_interface_object': |
| ('NoInterfaceObject' not in interface.extended_attributes), |
| 'conditional': interface.extended_attributes.get('Conditional', None), |
| 'add_opaque_roots': interface.extended_attributes.get('AddOpaqueRoots', |
| None), |
| 'get_opaque_root': interface.extended_attributes.get('GetOpaqueRoot', |
| None), |
| } |
| if is_global_interface(interface): |
| # Global interface references all interfaces. |
| referenced_interface_names = set( |
| interface_name |
| for interface_name in self.interfaces_info['all_interfaces'] |
| if not self.interfaces_info[interface_name]['unsupported'] and |
| not self.interfaces_info[interface_name]['is_callback_interface']) |
| referenced_interface_names.update(IdlType.callback_interfaces) |
| else: |
| # Build the set of referenced interfaces from this interface's members. |
| referenced_interface_names = set() |
| referenced_interface_names.update( |
| get_interface_type_names_from_typed_objects(interface.attributes)) |
| referenced_interface_names.update( |
| get_interface_type_names_from_typed_objects(interface.operations)) |
| referenced_interface_names.update( |
| get_interface_type_names_from_typed_objects(interface.constructors)) |
| referenced_interface_names.update( |
| get_interface_type_names_from_typed_objects( |
| definitions.callback_functions.values())) |
| |
| # Build the set of #includes in the header file. Try to keep this small |
| # to avoid circular dependency problems. |
| header_includes = set() |
| if interface.parent: |
| header_includes.add(self.wrapper_header_from_interface_name( |
| interface.parent)) |
| header_includes.add(get_implementation_header_path(interface_info)) |
| |
| attributes = [contexts.attribute_context(interface, attribute, definitions) |
| for attribute in interface.attributes] |
| constructor = contexts.get_constructor_context(self.expression_generator, |
| interface) |
| methods = contexts.get_method_contexts(self.expression_generator, interface) |
| constants = [contexts.constant_context(c) for c in interface.constants] |
| |
| # Get a list of all the unsupported property names, and remove the |
| # unsupported ones from their respective lists |
| attributes, unsupported_attribute_names = split_unsupported_properties( |
| attributes) |
| methods, unsupported_method_names = split_unsupported_properties(methods) |
| constants, unsupported_constant_names = split_unsupported_properties( |
| constants) |
| |
| # Build a set of all interfaces referenced by this interface. |
| referenced_interfaces = {} |
| for interface_name in referenced_interface_names: |
| # Get information for classes related to this interface. |
| for referenced_interface_context in self.referenced_interface_contexts( |
| interface_name): |
| # Store it in the dictionary using fully_qualified_name as the key so |
| # we can avoid duplicates |
| key = referenced_interface_context['fully_qualified_name'] |
| referenced_interfaces[key] = referenced_interface_context |
| # Now that we have all the referenced interfaces at most once, just get |
| # the values(), since the key is duplicated there and we no longer need |
| # to lookup by name. |
| referenced_interfaces = referenced_interfaces.values() |
| |
| all_interfaces = [] |
| for interface_name in referenced_interface_names: |
| if interface_name not in IdlType.callback_interfaces and not ( |
| self.interfaces_info[interface_name]['unsupported']): |
| all_interfaces.append({ |
| 'name': interface_name, |
| 'conditional': self.interfaces_info[interface_name]['conditional'], |
| }) |
| |
| context['implementation_includes'] = sorted( |
| (interface['include'] for interface in referenced_interfaces)) |
| context['header_includes'] = sorted(header_includes) |
| context['attributes'] = [a for a in attributes if not a['is_static']] |
| context['static_attributes'] = [a for a in attributes if a['is_static']] |
| context['constants'] = constants |
| context['constructor'] = constructor |
| context['named_constructor'] = interface.extended_attributes.get( |
| 'NamedConstructor', None) |
| context['interface'] = interface |
| context['operations'] = [m for m in methods if not m['is_static']] |
| context['static_operations'] = [m for m in methods if m['is_static']] |
| context['unsupported_interface_properties'] = set( |
| unsupported_attribute_names + unsupported_method_names + |
| unsupported_constant_names) |
| context['unsupported_constructor_properties'] = set( |
| unsupported_constant_names) |
| if interface.parent: |
| context['parent_interface'] = ( |
| '::'.join(self.get_interface_components(interface.parent)) + '::%s' % |
| self.wrapper_class_from_interface_name(interface.parent)) |
| context['is_exception_interface'] = interface.is_exception |
| context['components'] = self.get_interface_components( |
| interface.idl_type.name) |
| context['forward_declarations'] = sorted( |
| referenced_interfaces, |
| key=lambda x: x['fully_qualified_name']) |
| context['all_interfaces'] = sorted(all_interfaces, key=lambda x: x['name']) |
| context['callback_functions'] = definitions.callback_functions.values() |
| context['enumerations'] = [ |
| contexts.enumeration_context(enumeration) |
| for enumeration in definitions.enumerations.values() |
| ] |
| |
| context['stringifier'] = contexts.stringifier_context(interface) |
| context['indexed_property_getter'] = contexts.special_method_context( |
| interface, get_indexed_property_getter(interface)) |
| context['indexed_property_setter'] = contexts.special_method_context( |
| interface, get_indexed_property_setter(interface)) |
| context['named_property_getter'] = contexts.special_method_context( |
| interface, get_named_property_getter(interface)) |
| context['named_property_setter'] = contexts.special_method_context( |
| interface, get_named_property_setter(interface)) |
| context['named_property_deleter'] = contexts.special_method_context( |
| interface, get_named_property_deleter(interface)) |
| |
| context['supports_indexed_properties'] = ( |
| context['indexed_property_getter'] or |
| context['indexed_property_setter']) |
| context['supports_named_properties'] = (context['named_property_setter'] or |
| context['named_property_getter'] or |
| context['named_property_deleter']) |
| |
| return context |
| |
| ################################################################################ |
| |
| |
| def main(argv): |
| # If file itself executed, cache templates |
| try: |
| cache_dir = argv[1] |
| templates_dir = argv[2] |
| dummy_filename = argv[3] |
| except IndexError: |
| print 'Usage: %s CACHE_DIR TEMPLATES_DIR DUMMY_FILENAME' % argv[0] |
| return 1 |
| |
| # Cache templates. |
| # Whether the path is absolute or relative affects the cache file name. Use |
| # the absolute path to ensure that the same path is used when we populate the |
| # cache here and when it's read during code generation. |
| jinja_env = initialize_jinja_env(cache_dir, os.path.abspath(templates_dir)) |
| template_filenames = [ |
| filename |
| for filename in os.listdir(templates_dir) |
| # Skip .svn, directories, etc. |
| if filename.endswith(('.template')) |
| ] |
| assert template_filenames, 'Expected at least one template to be cached.' |
| shared_template_filenames = [ |
| filename |
| for filename in os.listdir(SHARED_TEMPLATES_DIR) |
| # Skip .svn, directories, etc. |
| if filename.endswith(('.template')) |
| ] |
| for template_filename in template_filenames + shared_template_filenames: |
| jinja_env.get_template(template_filename) |
| |
| # Create a dummy file as output for the build system, |
| # since filenames of individual cache files are unpredictable and opaque |
| # (they are hashes of the template path, which varies based on environment) |
| with open(dummy_filename, 'w') as dummy_file: |
| pass # |open| creates or touches the file |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv)) |