Home | History | Annotate | Download | only in scripts
      1 # Copyright (C) 2013 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the name of Google Inc. nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 """Generate Blink V8 bindings (.h and .cpp files).
     30 
     31 If run itself, caches Jinja templates (and creates dummy file for build,
     32 since cache filenames are unpredictable and opaque).
     33 
     34 This module is *not* concurrency-safe without care: bytecode caching creates
     35 a race condition on cache *write* (crashes if one process tries to read a
     36 partially-written cache). However, if you pre-cache the templates (by running
     37 the module itself), then you can parallelize compiling individual files, since
     38 cache *reading* is safe.
     39 
     40 Input: An object of class IdlDefinitions, containing an IDL interface X
     41 Output: V8X.h and V8X.cpp
     42 
     43 Design doc: http://www.chromium.org/developers/design-documents/idl-compiler
     44 """
     45 
     46 import os
     47 import posixpath
     48 import re
     49 import sys
     50 
     51 # Path handling for libraries and templates
     52 # Paths have to be normalized because Jinja uses the exact template path to
     53 # determine the hash used in the cache filename, and we need a pre-caching step
     54 # to be concurrency-safe. Use absolute path because __file__ is absolute if
     55 # module is imported, and relative if executed directly.
     56 # If paths differ between pre-caching and individual file compilation, the cache
     57 # is regenerated, which causes a race condition and breaks concurrent build,
     58 # since some compile processes will try to read the partially written cache.
     59 module_path, module_filename = os.path.split(os.path.realpath(__file__))
     60 third_party_dir = os.path.normpath(os.path.join(
     61     module_path, os.pardir, os.pardir, os.pardir, os.pardir))
     62 templates_dir = os.path.normpath(os.path.join(
     63     module_path, os.pardir, 'templates'))
     64 # Make sure extension is .py, not .pyc or .pyo, so doesn't depend on caching
     65 module_pyname = os.path.splitext(module_filename)[0] + '.py'
     66 
     67 # jinja2 is in chromium's third_party directory.
     68 # Insert at 1 so at front to override system libraries, and
     69 # after path[0] == invoking script dir
     70 sys.path.insert(1, third_party_dir)
     71 import jinja2
     72 
     73 import idl_types
     74 from idl_types import IdlType
     75 import v8_callback_interface
     76 import v8_dictionary
     77 from v8_globals import includes, interfaces
     78 import v8_interface
     79 import v8_types
     80 from v8_utilities import capitalize, cpp_name, conditional_string, v8_class_name
     81 from utilities import KNOWN_COMPONENTS
     82 
     83 
     84 def render_template(interface_info, header_template, cpp_template,
     85                     template_context):
     86     template_context['code_generator'] = module_pyname
     87 
     88     # Add includes for any dependencies
     89     template_context['header_includes'] = sorted(
     90         template_context['header_includes'])
     91     includes.update(interface_info.get('dependencies_include_paths', []))
     92     template_context['cpp_includes'] = sorted(includes)
     93 
     94     header_text = header_template.render(template_context)
     95     cpp_text = cpp_template.render(template_context)
     96     return header_text, cpp_text
     97 
     98 
     99 class CodeGeneratorBase(object):
    100     """Base class for v8 bindings generator and IDL dictionary impl generator"""
    101 
    102     def __init__(self, interfaces_info, cache_dir, output_dir):
    103         interfaces_info = interfaces_info or {}
    104         self.interfaces_info = interfaces_info
    105         self.jinja_env = initialize_jinja_env(cache_dir)
    106         self.output_dir = output_dir
    107 
    108         # Set global type info
    109         idl_types.set_ancestors(interfaces_info['ancestors'])
    110         IdlType.set_callback_interfaces(interfaces_info['callback_interfaces'])
    111         IdlType.set_dictionaries(interfaces_info['dictionaries'])
    112         IdlType.set_implemented_as_interfaces(interfaces_info['implemented_as_interfaces'])
    113         IdlType.set_garbage_collected_types(interfaces_info['garbage_collected_interfaces'])
    114         IdlType.set_will_be_garbage_collected_types(interfaces_info['will_be_garbage_collected_interfaces'])
    115         v8_types.set_component_dirs(interfaces_info['component_dirs'])
    116 
    117     def generate_code(self, definitions, definition_name):
    118         """Returns .h/.cpp code as ((path, content)...)."""
    119         # Set local type info
    120         IdlType.set_callback_functions(definitions.callback_functions.keys())
    121         IdlType.set_enums((enum.name, enum.values)
    122                           for enum in definitions.enumerations.values())
    123         return self.generate_code_internal(definitions, definition_name)
    124 
    125     def generate_code_internal(self, definitions, definition_name):
    126         # This should be implemented in subclasses.
    127         raise NotImplementedError()
    128 
    129 
    130 class CodeGeneratorV8(CodeGeneratorBase):
    131     def __init__(self, interfaces_info, cache_dir, output_dir):
    132         CodeGeneratorBase.__init__(self, interfaces_info, cache_dir, output_dir)
    133 
    134     def output_paths(self, definition_name):
    135         header_path = posixpath.join(self.output_dir,
    136                                      'V8%s.h' % definition_name)
    137         cpp_path = posixpath.join(self.output_dir, 'V8%s.cpp' % definition_name)
    138         return header_path, cpp_path
    139 
    140     def generate_code_internal(self, definitions, definition_name):
    141         if definition_name in definitions.interfaces:
    142             return self.generate_interface_code(
    143                 definitions, definition_name,
    144                 definitions.interfaces[definition_name])
    145         if definition_name in definitions.dictionaries:
    146             return self.generate_dictionary_code(
    147                 definitions, definition_name,
    148                 definitions.dictionaries[definition_name])
    149         raise ValueError('%s is not in IDL definitions' % definition_name)
    150 
    151     def generate_interface_code(self, definitions, interface_name, interface):
    152         # Store other interfaces for introspection
    153         interfaces.update(definitions.interfaces)
    154 
    155         # Select appropriate Jinja template and contents function
    156         if interface.is_callback:
    157             header_template_filename = 'callback_interface.h'
    158             cpp_template_filename = 'callback_interface.cpp'
    159             interface_context = v8_callback_interface.callback_interface_context
    160         else:
    161             header_template_filename = 'interface.h'
    162             cpp_template_filename = 'interface.cpp'
    163             interface_context = v8_interface.interface_context
    164         header_template = self.jinja_env.get_template(header_template_filename)
    165         cpp_template = self.jinja_env.get_template(cpp_template_filename)
    166 
    167         interface_info = self.interfaces_info[interface_name]
    168 
    169         template_context = interface_context(interface)
    170         # Add the include for interface itself
    171         template_context['header_includes'].add(interface_info['include_path'])
    172         header_text, cpp_text = render_template(
    173             interface_info, header_template, cpp_template, template_context)
    174         header_path, cpp_path = self.output_paths(interface_name)
    175         return (
    176             (header_path, header_text),
    177             (cpp_path, cpp_text),
    178         )
    179 
    180     def generate_dictionary_code(self, definitions, dictionary_name,
    181                                  dictionary):
    182         header_template = self.jinja_env.get_template('dictionary_v8.h')
    183         cpp_template = self.jinja_env.get_template('dictionary_v8.cpp')
    184         template_context = v8_dictionary.dictionary_context(dictionary)
    185         interface_info = self.interfaces_info[dictionary_name]
    186         # Add the include for interface itself
    187         template_context['header_includes'].add(interface_info['include_path'])
    188         header_text, cpp_text = render_template(
    189             interface_info, header_template, cpp_template, template_context)
    190         header_path, cpp_path = self.output_paths(dictionary_name)
    191         return (
    192             (header_path, header_text),
    193             (cpp_path, cpp_text),
    194         )
    195 
    196 
    197 class CodeGeneratorDictionaryImpl(CodeGeneratorBase):
    198     def __init__(self, interfaces_info, cache_dir, output_dir):
    199         CodeGeneratorBase.__init__(self, interfaces_info, cache_dir, output_dir)
    200 
    201     def output_paths(self, definition_name, interface_info):
    202         output_dir = posixpath.join(self.output_dir,
    203                                     interface_info['relative_dir'])
    204         header_path = posixpath.join(output_dir, '%s.h' % definition_name)
    205         cpp_path = posixpath.join(output_dir, '%s.cpp' % definition_name)
    206         return header_path, cpp_path
    207 
    208     def generate_code_internal(self, definitions, definition_name):
    209         if not definition_name in definitions.dictionaries:
    210             raise ValueError('%s is not an IDL dictionary')
    211         dictionary = definitions.dictionaries[definition_name]
    212         interface_info = self.interfaces_info[definition_name]
    213         header_template = self.jinja_env.get_template('dictionary_impl.h')
    214         cpp_template = self.jinja_env.get_template('dictionary_impl.cpp')
    215         template_context = v8_dictionary.dictionary_impl_context(
    216             dictionary, self.interfaces_info)
    217         header_text, cpp_text = render_template(
    218             interface_info, header_template, cpp_template, template_context)
    219         header_path, cpp_path = self.output_paths(
    220             definition_name, interface_info)
    221         return (
    222             (header_path, header_text),
    223             (cpp_path, cpp_text),
    224         )
    225 
    226 
    227 def initialize_jinja_env(cache_dir):
    228     jinja_env = jinja2.Environment(
    229         loader=jinja2.FileSystemLoader(templates_dir),
    230         # Bytecode cache is not concurrency-safe unless pre-cached:
    231         # if pre-cached this is read-only, but writing creates a race condition.
    232         bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir),
    233         keep_trailing_newline=True,  # newline-terminate generated files
    234         lstrip_blocks=True,  # so can indent control flow tags
    235         trim_blocks=True)
    236     jinja_env.filters.update({
    237         'blink_capitalize': capitalize,
    238         'conditional': conditional_if_endif,
    239         'exposed': exposed_if,
    240         'per_context_enabled': per_context_enabled_if,
    241         'runtime_enabled': runtime_enabled_if,
    242         })
    243     return jinja_env
    244 
    245 
    246 def generate_indented_conditional(code, conditional):
    247     # Indent if statement to level of original code
    248     indent = re.match(' *', code).group(0)
    249     return ('%sif (%s) {\n' % (indent, conditional) +
    250             '    %s\n' % '\n    '.join(code.splitlines()) +
    251             '%s}\n' % indent)
    252 
    253 
    254 # [Conditional]
    255 def conditional_if_endif(code, conditional_string):
    256     # Jinja2 filter to generate if/endif directive blocks
    257     if not conditional_string:
    258         return code
    259     return ('#if %s\n' % conditional_string +
    260             code +
    261             '#endif // %s\n' % conditional_string)
    262 
    263 
    264 # [Exposed]
    265 def exposed_if(code, exposed_test):
    266     if not exposed_test:
    267         return code
    268     return generate_indented_conditional(code, 'context && (%s)' % exposed_test)
    269 
    270 
    271 # [PerContextEnabled]
    272 def per_context_enabled_if(code, per_context_enabled_function):
    273     if not per_context_enabled_function:
    274         return code
    275     return generate_indented_conditional(code, 'context && context->isDocument() && %s(toDocument(context))' % per_context_enabled_function)
    276 
    277 
    278 # [RuntimeEnabled]
    279 def runtime_enabled_if(code, runtime_enabled_function_name):
    280     if not runtime_enabled_function_name:
    281         return code
    282     return generate_indented_conditional(code, '%s()' % runtime_enabled_function_name)
    283 
    284 
    285 ################################################################################
    286 
    287 def main(argv):
    288     # If file itself executed, cache templates
    289     try:
    290         cache_dir = argv[1]
    291         dummy_filename = argv[2]
    292     except IndexError as err:
    293         print 'Usage: %s CACHE_DIR DUMMY_FILENAME' % argv[0]
    294         return 1
    295 
    296     # Cache templates
    297     jinja_env = initialize_jinja_env(cache_dir)
    298     template_filenames = [filename for filename in os.listdir(templates_dir)
    299                           # Skip .svn, directories, etc.
    300                           if filename.endswith(('.cpp', '.h'))]
    301     for template_filename in template_filenames:
    302         jinja_env.get_template(template_filename)
    303 
    304     # Create a dummy file as output for the build system,
    305     # since filenames of individual cache files are unpredictable and opaque
    306     # (they are hashes of the template path, which varies based on environment)
    307     with open(dummy_filename, 'w') as dummy_file:
    308         pass  # |open| creates or touches the file
    309 
    310 
    311 if __name__ == '__main__':
    312     sys.exit(main(sys.argv))
    313