# 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 import logging # 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) import jinja2 import idl_types from idl_types import IdlType import dart_callback_interface import dart_interface import dart_types from dart_utilities import DartUtilities from utilities import write_pickle_file, idl_filename_to_interface_name from v8_globals import includes, interfaces 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'])) dart_types.set_component_dirs(dict( (interface_name, interface_info['component_dir']) for interface_name, interface_info in interfaces_info.iteritems())) def generate_code(self, definitions, interface_name, idl_filename, idl_pickle_filename, only_if_changed): """Returns .h/.cpp/.dart code as (header_text, cpp_text, dart_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' dart_template_filename = 'callback_interface_dart.template' generate_contents = dart_callback_interface.generate_callback_interface else: header_template_filename = 'interface_h.template' cpp_template_filename = 'interface_cpp.template' dart_template_filename = 'interface_dart.template' generate_contents = dart_interface.interface_context header_template = self.jinja_env.get_template(header_template_filename) cpp_template = self.jinja_env.get_template(cpp_template_filename) dart_template = self.jinja_env.get_template(dart_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', [])) template_contents['cpp_includes'] = sorted(includes) # If PrivateDart is set, read the custom dart file and add it to our # template parameters. if 'PrivateDart' in interface.extended_attributes: template_contents['private_dart'] = True 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 = {'component_dir': interface_info['component_dir'], 'name': template_contents['interface_name'], 'parent_interface': template_contents['parent_interface'], 'has_resolver': template_contents['interface_name'], 'native_entries': sorted(template_contents['native_entries'], key=lambda(x): x['blink_entry']), } 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) dart_text = dart_template.render(template_contents) return header_text, cpp_text, dart_text def load_global_pickles(self, global_entries): # List of all interfaces and callbacks for global code generation. world = {'interfaces': [], 'callbacks': []} # Load all pickled data for each interface. for (directory, file_list) in global_entries: for filename in file_list: if os.path.splitext(filename)[1] == '.dart': # Special case: any .dart files in the list should be added # to dart_sky.dart directly, but don't need to be processed. interface_name = os.path.splitext(os.path.basename(filename))[0] world['interfaces'].append({'name': "Custom" + interface_name}) continue interface_name = idl_filename_to_interface_name(filename) idl_pickle_filename = interface_name + "_globals.pickle" idl_pickle_filename = os.path.join(directory, idl_pickle_filename) if not os.path.exists(idl_pickle_filename): logging.warn("Missing %s" % idl_pickle_filename) continue with open(idl_pickle_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 'callback' in idl_world: # FIXME: Why are some of these None? if idl_world['callback']: world['callbacks'].append(idl_world['callback']) world['interfaces'] = sorted(world['interfaces'], key=lambda (x): x['name']) world['callbacks'] = sorted(world['callbacks'], key=lambda (x): x['name']) return world # Generates global file for all interfaces. def generate_globals(self, global_entries): template_contents = self.load_global_pickles(global_entries) template_contents['code_generator'] = module_pyname header_template_filename = 'global_h.template' header_template = self.jinja_env.get_template(header_template_filename) header_text = header_template.render(template_contents) cpp_template_filename = 'global_cpp.template' cpp_template = self.jinja_env.get_template(cpp_template_filename) cpp_text = cpp_template.render(template_contents) return header_text, cpp_text # Generates global dart blink file for all interfaces. def generate_dart_blink(self, global_entries): template_contents = self.load_global_pickles(global_entries) template_contents['code_generator'] = module_pyname template_filename = 'dart_blink.template' template = self.jinja_env.get_template(template_filename) text = template.render(template_contents) return 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, }) return jinja_env ################################################################################ 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))