# Copyright (C) 2013 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Generate Blink C++ bindings (.h and .cpp files) for use by Dart:HTML. If run itself, caches Jinja templates (and creates dummy file for build, since cache filenames are unpredictable and opaque). This module is *not* concurrency-safe without care: bytecode caching creates a race condition on cache *write* (crashes if one process tries to read a partially-written cache). However, if you pre-cache the templates (by running the module itself), then you can parallelize compiling individual files, since cache *reading* is safe. Input: An object of class IdlDefinitions, containing an IDL interface X Output: DartX.h and DartX.cpp Design doc: http://www.chromium.org/developers/design-documents/idl-compiler """ import os import cPickle as pickle import re import sys # Path handling for libraries and templates # Paths have to be normalized because Jinja uses the exact template path to # determine the hash used in the cache filename, and we need a pre-caching step # to be concurrency-safe. Use absolute path because __file__ is absolute if # module is imported, and relative if executed directly. # If paths differ between pre-caching and individual file compilation, the cache # is regenerated, which causes a race condition and breaks concurrent build, # since some compile processes will try to read the partially written cache. module_path, module_filename = os.path.split(os.path.realpath(__file__)) third_party_dir = os.path.normpath( os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pardir, os.pardir)) templates_dir = os.path.normpath(os.path.join(module_path, 'templates')) # Make sure extension is .py, not .pyc or .pyo, so doesn't depend on caching module_pyname = os.path.splitext(module_filename)[0] + '.py' # jinja2 is in chromium's third_party directory. # Insert at 1 so at front to override system libraries, and # after path[0] == invoking script dir sys.path.insert(1, third_party_dir) # Add the base compiler scripts to the path here as in compiler.py dart_script_path = os.path.dirname(os.path.abspath(__file__)) script_path = os.path.join( os.path.dirname(os.path.dirname(dart_script_path)), 'scripts') sys.path.extend([script_path]) import jinja2 import idl_types from idl_types import IdlType from utilities import write_pickle_file from v8_globals import includes from dart_utilities import DartUtilities # TODO(jacobr): remove this hacked together list. INTERFACES_WITHOUT_RESOLVERS = frozenset([ 'TypeConversions', 'GCObservation', 'InternalProfilers', 'InternalRuntimeFlags', 'InternalSettings', 'InternalSettingsGenerated', 'Internals', 'LayerRect', 'LayerRectList', 'MallocStatistics', 'TypeConversions' ]) class CodeGeneratorDart(object): def __init__(self, interfaces_info, cache_dir): interfaces_info = interfaces_info or {} self.interfaces_info = interfaces_info self.jinja_env = initialize_jinja_env(cache_dir) # Set global type info idl_types.set_ancestors( dict((interface_name, interface_info['ancestors']) for interface_name, interface_info in interfaces_info. iteritems() if interface_info['ancestors'])) IdlType.set_callback_interfaces( set(interface_name for interface_name, interface_info in interfaces_info.iteritems() if interface_info['is_callback_interface'])) IdlType.set_implemented_as_interfaces( dict((interface_name, interface_info['implemented_as']) for interface_name, interface_info in interfaces_info. iteritems() if interface_info['implemented_as'])) IdlType.set_garbage_collected_types( set(interface_name for interface_name, interface_info in interfaces_info.iteritems() if 'GarbageCollected' in interface_info['inherited_extended_attributes'])) def generate_code(self, definitions, interface_name, idl_pickle_filename, only_if_changed): """Returns .h/.cpp code as (header_text, cpp_text).""" try: interface = definitions.interfaces[interface_name] except KeyError: raise Exception('%s not in IDL definitions' % interface_name) # Store other interfaces for introspection interfaces.update(definitions.interfaces) # Set local type info IdlType.set_callback_functions(definitions.callback_functions.keys()) IdlType.set_enums((enum.name, enum.values) for enum in definitions.enumerations.values()) # Select appropriate Jinja template and contents function if interface.is_callback: header_template_filename = 'callback_interface_h.template' cpp_template_filename = 'callback_interface_cpp.template' generate_contents = dart_callback_interface.generate_callback_interface else: header_template_filename = 'interface_h.template' cpp_template_filename = 'interface_cpp.template' generate_contents = dart_interface.generate_interface header_template = self.jinja_env.get_template(header_template_filename) cpp_template = self.jinja_env.get_template(cpp_template_filename) # Generate contents (input parameters for Jinja) template_contents = generate_contents(interface) template_contents['code_generator'] = module_pyname # Add includes for interface itself and any dependencies interface_info = self.interfaces_info[interface_name] template_contents['header_includes'].add(interface_info['include_path']) template_contents['header_includes'] = sorted( template_contents['header_includes']) includes.update(interface_info.get('dependencies_include_paths', [])) # Remove includes that are not needed for Dart and trigger fatal # compile warnings if included. These IDL files need to be # imported by Dart to generate the list of events but the # associated header files do not contain any code used by Dart. includes.discard('core/dom/GlobalEventHandlers.h') includes.discard('core/frame/DOMWindowEventHandlers.h') template_contents['cpp_includes'] = sorted(includes) idl_world = {'interface': None, 'callback': None} # Load the pickle file for this IDL. if os.path.isfile(idl_pickle_filename): with open(idl_pickle_filename) as idl_pickle_file: idl_global_data = pickle.load(idl_pickle_file) idl_pickle_file.close() idl_world['interface'] = idl_global_data['interface'] idl_world['callback'] = idl_global_data['callback'] if 'interface_name' in template_contents: interface_global = { 'name': template_contents['interface_name'], 'parent_interface': template_contents['parent_interface'], 'is_active_dom_object': template_contents['is_active_dom_object'], 'is_event_target': template_contents['is_event_target'], 'has_resolver': template_contents['interface_name'] not in INTERFACES_WITHOUT_RESOLVERS, 'is_node': template_contents['is_node'], 'conditional_string': template_contents['conditional_string'], } idl_world['interface'] = interface_global else: callback_global = {'name': template_contents['cpp_class']} idl_world['callback'] = callback_global write_pickle_file(idl_pickle_filename, idl_world, only_if_changed) # Render Jinja templates header_text = header_template.render(template_contents) cpp_text = cpp_template.render(template_contents) return header_text, cpp_text # Generates global file for all interfaces. def generate_globals(self, output_directory): header_template_filename = 'global_h.template' cpp_template_filename = 'global_cpp.template' # Delete the global pickle file we'll rebuild from each pickle generated # for each IDL file '(%s_globals.pickle) % interface_name'. global_pickle_filename = os.path.join(output_directory, 'global.pickle') if os.path.isfile(global_pickle_filename): os.remove(global_pickle_filename) # List of all interfaces and callbacks for global code generation. world = {'interfaces': [], 'callbacks': []} # Load all pickled data for each interface. listing = os.listdir(output_directory) for filename in listing: if filename.endswith('_globals.pickle'): idl_filename = os.path.join(output_directory, filename) with open(idl_filename) as idl_pickle_file: idl_world = pickle.load(idl_pickle_file) if 'interface' in idl_world: # FIXME: Why are some of these None? if idl_world['interface']: world['interfaces'].append(idl_world['interface']) if 'callbacks' in idl_world: # FIXME: Why are some of these None? if idl_world['callbacks']: world['callbacks'].append(idl_world['callback']) idl_pickle_file.close() world['interfaces'] = sorted( world['interfaces'], key=lambda (x): x['name']) world['callbacks'] = sorted( world['callbacks'], key=lambda (x): x['name']) template_contents = world template_contents['code_generator'] = module_pyname header_template = self.jinja_env.get_template(header_template_filename) header_text = header_template.render(template_contents) cpp_template = self.jinja_env.get_template(cpp_template_filename) cpp_text = cpp_template.render(template_contents) return header_text, cpp_text def initialize_jinja_env(cache_dir): jinja_env = jinja2.Environment( loader=jinja2.FileSystemLoader(templates_dir), # 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) jinja_env.filters.update({ 'blink_capitalize': DartUtilities.capitalize, 'conditional': conditional_if_endif, 'runtime_enabled': runtime_enabled_if, }) return jinja_env # [Conditional] def conditional_if_endif(code, conditional_string): # Jinja2 filter to generate if/endif directive blocks if not conditional_string: return code return ('#if %s\n' % conditional_string + code + '#endif // %s\n' % conditional_string) # [RuntimeEnabled] def runtime_enabled_if(code, runtime_enabled_function_name): if not runtime_enabled_function_name: return code # Indent if statement to level of original code indent = re.match(' *', code).group(0) return ('%sif (%s())\n' % (indent, runtime_enabled_function_name) + ' %s' % code) ################################################################################ def main(argv): # If file itself executed, cache templates try: cache_dir = argv[1] dummy_filename = argv[2] except IndexError as err: print 'Usage: %s OUTPUT_DIR DUMMY_FILENAME' % argv[0] return 1 # Cache templates jinja_env = initialize_jinja_env(cache_dir) template_filenames = [ filename for filename in os.listdir(templates_dir) # Skip .svn, directories, etc. if filename.endswith(('.cpp', '.h', '.template')) ] for template_filename in 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))