| # 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.""" |
| |
| from __future__ import print_function |
| |
| import os |
| import posixpath |
| import re |
| import sys |
| |
| from idl_types import set_ancestors, IdlType |
| from itertools import groupby |
| from v8_globals import includes |
| from v8_interface import constant_filters |
| from v8_types import set_component_dirs |
| from v8_methods import method_filters |
| from v8_utilities import capitalize |
| from utilities import (idl_filename_to_component, |
| is_valid_component_dependency, format_remove_duplicates, |
| format_blink_cpp_source_code, to_snake_case, |
| normalize_path) |
| import v8_utilities |
| |
| # 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 |
| from jinja2.filters import make_attrgetter, environmentfilter |
| |
| |
| 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, 'execution_context && (%s)' % exposed_test) |
| |
| |
| # [SecureContext] |
| def secure_context_if(code, secure_context_test): |
| if secure_context_test is None: |
| return code |
| return generate_indented_conditional(code, secure_context_test) |
| |
| |
| # [RuntimeEnabled] |
| def origin_trial_enabled_if(code, |
| origin_trial_feature_name, |
| execution_context=None): |
| if not origin_trial_feature_name: |
| return code |
| |
| function = v8_utilities.origin_trial_function_call( |
| origin_trial_feature_name, execution_context) |
| return generate_indented_conditional(code, function) |
| |
| |
| # [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) |
| |
| |
| @environmentfilter |
| def do_stringify_key_group_by(environment, value, attribute): |
| expr = make_attrgetter(environment, attribute) |
| key = lambda item: '' if expr(item) is None else str(expr(item)) |
| return groupby(sorted(value, key=key), expr) |
| |
| |
| 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, |
| 'origin_trial_enabled': |
| origin_trial_enabled_if, |
| 'runtime_enabled': |
| runtime_enabled_if, |
| 'runtime_enabled_function': |
| v8_utilities.runtime_enabled_function, |
| 'secure_context': |
| secure_context_if |
| }) |
| jinja_env.filters.update(constant_filters()) |
| jinja_env.filters.update(method_filters()) |
| jinja_env.filters["stringifykeygroupby"] = do_stringify_key_group_by |
| return jinja_env |
| |
| |
| _BLINK_RELATIVE_PATH_PREFIXES = ('bindings/', 'core/', 'modules/', 'platform/') |
| |
| |
| def normalize_and_sort_includes(include_paths): |
| normalized_include_paths = set() |
| for include_path in include_paths: |
| match = re.search(r'/gen/(third_party/blink/.*)$', |
| posixpath.abspath(include_path)) |
| if match: |
| include_path = match.group(1) |
| elif include_path.startswith(_BLINK_RELATIVE_PATH_PREFIXES): |
| include_path = 'third_party/blink/renderer/' + include_path |
| normalized_include_paths.add(include_path) |
| return sorted(normalized_include_paths) |
| |
| |
| def render_template(template, context): |
| filename = str(template.filename) |
| filename = filename[filename.rfind('third_party'):] |
| filename = normalize_path(filename) |
| |
| 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']) |
| IdlType.set_garbage_collected_types(interfaces_info['dictionaries']) |
| set_component_dirs(interfaces_info['component_dirs']) |
| |
| def render_templates(self, |
| include_paths, |
| header_template, |
| cpp_template, |
| context, |
| component=None): |
| context['code_generator'] = self.generator_name |
| |
| # Add includes for any dependencies |
| 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) |
| |
| cpp_includes = set(context.get('cpp_includes', [])) |
| context['cpp_includes'] = normalize_and_sort_includes(cpp_includes |
| | includes) |
| context['header_includes'] = normalize_and_sort_includes( |
| context['header_includes']) |
| |
| header_text = render_template(header_template, context) |
| cpp_text = render_template(cpp_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 normalize_this_header_path(self, header_path): |
| header_path = normalize_path(header_path) |
| match = re.search('(third_party/blink/.*)$', header_path) |
| assert match, 'Unkown style of path to output: ' + header_path |
| return match.group(1) |
| |
| |
| 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)) |