blob: 66838f80188ade9b3afd2ae8fed5568fa72ff3b8 [file] [log] [blame]
# 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
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, test_result=None):
if not secure_context_test:
return code
if test_result:
return generate_indented_conditional(code, test_result)
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(
# Bytecode cache is not concurrency-safe unless pre-cached:
# if pre-cached this is read-only, but writing creates a race condition.
keep_trailing_newline=True, # newline-terminate generated files
lstrip_blocks=True, # so can indent control flow tags
'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})
return jinja_env
def normalize_and_sort_includes(include_paths, snake_case):
normalized_include_paths = []
for include_path in include_paths:
match ='/gen/blink/(.*)$', posixpath.abspath(include_path))
if match:
include_path =
if snake_case:
match ='/([^/]+)\.h$', include_path)
if match:
name =
if name.lower() != name:
include_path = include_path[0:match.start(1)] + to_snake_case(name) + '.h'
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, snake_case):
self.generator_name = generator_name
self.info_provider = info_provider
self.jinja_env = initialize_jinja_env(cache_dir)
self.output_dir = output_dir
self.snake_case_generated_files = snake_case
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
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'], self.snake_case_generated_files)
for include_path in include_paths:
if component:
dependency = idl_filename_to_component(include_path)
assert is_valid_component_dependency(component, dependency)
template_context['cpp_includes'] = normalize_and_sort_includes(includes, self.snake_case_generated_files)
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
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:
# 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__':