| # Copyright 2016 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. | |
| # pylint: disable=import-error,print-statement,relative-import | |
| """Plumbing for a Jinja-based code generator, including CodeGeneratorBase, a base class for all generators.""" | |
| import os | |
| import posixpath | |
| import re | |
| import sys | |
| from idl_types import set_ancestors, IdlType | |
| from v8_globals import includes | |
| from v8_interface import constant_filters | |
| from v8_types import set_component_dirs | |
| from v8_methods import method_filters | |
| import v8_utilities | |
| from v8_utilities import capitalize, unique_by | |
| from utilities import (idl_filename_to_component, is_valid_component_dependency, | |
| format_remove_duplicates, format_blink_cpp_source_code) | |
| # 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, _ = 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)) | |
| TEMPLATES_DIR = os.path.normpath(os.path.join( | |
| MODULE_PATH, os.pardir, 'templates')) | |
| # 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 | |
| def generate_indented_conditional(code, conditional): | |
| # Indent if statement to level of original code | |
| indent = re.match(' *', code).group(0) | |
| return ('%sif (%s) {\n' % (indent, conditional) + | |
| ' %s\n' % '\n '.join(code.splitlines()) + | |
| '%s}\n' % indent) | |
| # [Exposed] | |
| def exposed_if(code, exposed_test): | |
| if not exposed_test: | |
| return code | |
| return generate_indented_conditional(code, 'executionContext && (%s)' % exposed_test) | |
| # [SecureContext] | |
| def secure_context_if(code, secure_context_test): | |
| if not secure_context_test: | |
| return code | |
| return generate_indented_conditional(code, 'executionContext && (%s)' % secure_context_test) | |
| # [RuntimeEnabled] | |
| def runtime_enabled_if(code, name): | |
| if not name: | |
| return code | |
| function = v8_utilities.runtime_enabled_function(name) | |
| return generate_indented_conditional(code, function) | |
| 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': capitalize, | |
| 'exposed': exposed_if, | |
| 'format_blink_cpp_source_code': format_blink_cpp_source_code, | |
| 'format_remove_duplicates': format_remove_duplicates, | |
| 'runtime_enabled': runtime_enabled_if, | |
| 'runtime_enabled_function': v8_utilities.runtime_enabled_function, | |
| 'secure_context': secure_context_if, | |
| 'unique_by': unique_by}) | |
| jinja_env.filters.update(constant_filters()) | |
| jinja_env.filters.update(method_filters()) | |
| return jinja_env | |
| def normalize_and_sort_includes(include_paths): | |
| normalized_include_paths = [] | |
| for include_path in include_paths: | |
| match = re.search(r'/gen/blink/(.*)$', posixpath.abspath(include_path)) | |
| if match: | |
| include_path = match.group(1) | |
| normalized_include_paths.append(include_path) | |
| return sorted(normalized_include_paths) | |
| def render_template(template, context): | |
| filename = str(template.filename) | |
| filename = filename[filename.rfind("third_party"):] | |
| context["jinja_template_filename"] = filename | |
| return template.render(context) | |
| class CodeGeneratorBase(object): | |
| """Base class for jinja-powered jinja template generation. | |
| """ | |
| def __init__(self, generator_name, info_provider, cache_dir, output_dir): | |
| self.generator_name = generator_name | |
| self.info_provider = info_provider | |
| self.jinja_env = initialize_jinja_env(cache_dir) | |
| self.output_dir = output_dir | |
| self.set_global_type_info() | |
| def should_generate_code(self, definitions): | |
| return definitions.interfaces or definitions.dictionaries | |
| def set_global_type_info(self): | |
| interfaces_info = self.info_provider.interfaces_info | |
| set_ancestors(interfaces_info['ancestors']) | |
| IdlType.set_callback_interfaces(interfaces_info['callback_interfaces']) | |
| IdlType.set_dictionaries(interfaces_info['dictionaries']) | |
| IdlType.set_enums(self.info_provider.enumerations) | |
| IdlType.set_callback_functions(self.info_provider.callback_functions) | |
| IdlType.set_implemented_as_interfaces(interfaces_info['implemented_as_interfaces']) | |
| IdlType.set_garbage_collected_types(interfaces_info['garbage_collected_interfaces']) | |
| set_component_dirs(interfaces_info['component_dirs']) | |
| def render_template(self, include_paths, header_template, cpp_template, | |
| template_context, component=None): | |
| template_context['code_generator'] = self.generator_name | |
| # Add includes for any dependencies | |
| template_context['header_includes'] = normalize_and_sort_includes( | |
| template_context['header_includes']) | |
| for include_path in include_paths: | |
| if component: | |
| dependency = idl_filename_to_component(include_path) | |
| assert is_valid_component_dependency(component, dependency) | |
| includes.add(include_path) | |
| template_context['cpp_includes'] = normalize_and_sort_includes(includes) | |
| header_text = render_template(header_template, template_context) | |
| cpp_text = render_template(cpp_template, template_context) | |
| return header_text, cpp_text | |
| def generate_code(self, definitions, definition_name): | |
| """Invokes code generation. The [definitions] argument is a list of definitions, | |
| and the [definition_name] is the name of the definition | |
| """ | |
| # This should be implemented in subclasses. | |
| raise NotImplementedError() | |
| def main(argv): | |
| # If file itself executed, cache templates | |
| try: | |
| cache_dir = argv[1] | |
| dummy_filename = argv[2] | |
| except IndexError: | |
| print('Usage: %s CACHE_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(('.tmpl', '.txt'))] | |
| 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)) |