blob: 04c0fabcef2c80f40fc4d13f93a3e35df7f72191 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright 2017 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.
# This script reads the global interface data collected by
# compute_interfaces_info_overall.py, and writes out the code which adds
# bindings for origin-trial-enabled features at runtime.
import optparse
import os
import posixpath
import sys
from collections import defaultdict, namedtuple
from code_generator import (initialize_jinja_env, normalize_and_sort_includes,
render_template)
from idl_reader import IdlReader
from utilities import (create_component_info_provider, write_file,
idl_filename_to_component)
from v8_utilities import (binding_header_filename, v8_class_name,
v8_class_name_or_partial, origin_trial_feature_name)
# Make sure extension is .py, not .pyc or .pyo, so doesn't depend on caching
MODULE_PYNAME = os.path.splitext(os.path.basename(__file__))[0] + '.py'
OriginTrialInterfaceInfo = namedtuple(
'OriginTrialInterfaceInfo',
['name', 'v8_class', 'v8_class_or_partial', 'is_global'])
def get_install_functions(interfaces, feature_names):
"""Construct a list of V8 bindings installation functions for each feature
on each interface.
interfaces is a list of OriginTrialInterfaceInfo tuples
feature_names is a list of strings, containing names of features which can
be installed on those interfaces.
"""
return [{
'condition':
'RuntimeEnabledFeatures::%sEnabled' % feature_name,
'name':
feature_name,
'install_method':
'Install%s' % feature_name,
'interface_is_global':
interface_info.is_global,
'global_type_check_method':
interface_global_type_check_method(interface_info),
'v8_class':
interface_info.v8_class,
'v8_class_or_partial':
interface_info.v8_class_or_partial,
} for feature_name in feature_names for interface_info in interfaces]
def get_origin_trial_feature_names_from_interface(interface, runtime_features):
feature_names = set()
def add_if_not_none(value):
if value:
feature_names.add(value)
if interface.is_partial:
add_if_not_none(origin_trial_feature_name(interface, runtime_features))
for operation in interface.operations:
add_if_not_none(origin_trial_feature_name(operation, runtime_features))
for attribute in interface.attributes:
add_if_not_none(origin_trial_feature_name(attribute, runtime_features))
return feature_names
def read_idl_file(reader, idl_filename):
definitions = reader.read_idl_file(idl_filename)
interfaces = definitions.interfaces
includes = definitions.includes
# There should only be a single interface defined in an IDL file. Return it.
assert len(interfaces) == 1, (
"Expected one interface in file %r, found %d" %
(idl_filename, len(interfaces)))
return (list(interfaces.values())[0], includes)
def interface_is_global(interface):
return 'Global' in interface.extended_attributes
def interface_global_type_check_method(interface_info):
"""Generate the name of the method on ExecutionContext used to check if the
context matches the type of the interface, which is a global.
Returns None for non-global interfaces.
"""
if not interface_info.is_global:
return None
return 'Is%s' % interface_info.name
def origin_trial_features_info(info_provider, reader, idl_filenames,
target_component):
"""Read a set of IDL files and compile the mapping between interfaces and
the conditional features defined on them.
Returns a tuple (features_for_type, types_for_feature, includes):
- features_for_type is a mapping of interface->feature
- types_for_feature is the reverse mapping: feature->interface
- includes is a set of header files which need to be included in the
generated implementation code.
"""
features_for_type = defaultdict(set)
types_for_feature = defaultdict(set)
include_files = set()
runtime_features = info_provider.component_info['runtime_enabled_features']
for idl_filename in idl_filenames:
interface, includes = read_idl_file(reader, idl_filename)
feature_names = get_origin_trial_feature_names_from_interface(
interface, runtime_features)
# If this interface is a mixin, we don't generate V8 bindings code for
# it.
# TODO(crbug.com/1061995): This incorrectly ignores includes in the
# mixin idl like "SomeInterface includes MixinInterface".
if interface.is_mixin:
continue
# If this interface include another one,
# it inherits any conditional features from it.
for include in includes:
assert include.interface == interface.name, (
"'includes' interface identifier %r in file %r should be %r" %
(include.interface, idl_filename, interface.name))
mixin, _ = read_idl_file(
reader,
info_provider.interfaces_info[include.mixin].get('full_path'))
feature_names |= get_origin_trial_feature_names_from_interface(
mixin, runtime_features)
feature_names = list(feature_names)
if feature_names:
is_global = interface_is_global(interface)
if interface.is_partial:
# For partial interfaces, we need to generate different
# |include_files| if the parent interface is in a different
# component.
parent_interface_info = \
info_provider.interfaces_info[interface.name]
parent_interface, _ = read_idl_file(
reader, parent_interface_info.get('full_path'))
is_global = is_global or interface_is_global(parent_interface)
parent_component = idl_filename_to_component(
parent_interface_info.get('full_path'))
if interface.is_partial and target_component != parent_component:
include_files.add('bindings/%s/v8/%s' % (
parent_component, binding_header_filename(interface.name)))
include_files.add(
'bindings/%s/v8/%s' %
(target_component,
binding_header_filename(interface.name + 'Partial')))
else:
include_files.add('bindings/%s/v8/%s' % (
target_component, binding_header_filename(interface.name)))
# If this is a partial interface in the same component as
# its parent, then treat it as a non-partial interface.
interface.is_partial = False
interface_info = OriginTrialInterfaceInfo(
interface.name, v8_class_name(interface),
v8_class_name_or_partial(interface), is_global)
for feature_name in feature_names:
features_for_type[interface_info].add(feature_name)
types_for_feature[feature_name].add(interface_info)
return features_for_type, types_for_feature, include_files
def origin_trial_features_context(generator_name, feature_info):
context = {'code_generator': generator_name}
# Unpack the feature info tuple.
features_for_type, types_for_feature, include_files = feature_info
# Add includes needed for cpp code and normalize.
include_files.update([
'core/context_features/context_feature_settings.h',
'core/execution_context/execution_context.h',
'core/frame/frame.h',
'core/origin_trials/origin_trials.h',
'platform/bindings/origin_trial_features.h',
'platform/bindings/script_state.h',
'platform/bindings/v8_per_context_data.h',
'platform/runtime_enabled_features.h',
# TODO(iclelland): Remove the need to explicitly include this; it is
# here because the ContextFeatureSettings code needs it.
'bindings/core/v8/v8_window.h',
])
context['includes'] = normalize_and_sort_includes(include_files)
# For each interface, collect a list of bindings installation functions to
# call, organized by conditional feature.
context['installers_by_interface'] = [{
'name':
interface_info.name,
'is_global':
interface_info.is_global,
'v8_class':
interface_info.v8_class,
'installers':
get_install_functions([interface_info], feature_names)
} for interface_info, feature_names in features_for_type.items()]
context['installers_by_interface'].sort(key=lambda x: x['name'])
# For each conditional feature, collect a list of bindings installation
# functions to call, organized by interface.
context['installers_by_feature'] = [{
'name':
feature_name,
'name_constant':
'OriginTrialFeature::k%s' % feature_name,
'installers':
get_install_functions(interfaces, [feature_name])
} for feature_name, interfaces in types_for_feature.items()]
context['installers_by_feature'].sort(key=lambda x: x['name'])
return context
def parse_options():
parser = optparse.OptionParser()
parser.add_option(
'--cache-directory',
help='cache directory, defaults to output directory')
parser.add_option('--output-directory')
parser.add_option('--info-dir')
parser.add_option(
'--target-component',
type='choice',
choices=['core', 'modules'],
help='target component to generate code')
parser.add_option('--idl-files-list')
options, _ = parser.parse_args()
if options.output_directory is None:
parser.error('Must specify output directory using --output-directory.')
return options
def generate_origin_trial_features(info_provider, options, idl_filenames):
reader = IdlReader(info_provider.interfaces_info, options.cache_directory)
jinja_env = initialize_jinja_env(options.cache_directory)
# Extract the bidirectional mapping of conditional features <-> interfaces
# from the global info provider and the supplied list of IDL files.
feature_info = origin_trial_features_info(
info_provider, reader, idl_filenames, options.target_component)
# Convert that mapping into the context required for the Jinja2 templates.
template_context = origin_trial_features_context(MODULE_PYNAME,
feature_info)
file_basename = 'origin_trial_features_for_%s' % options.target_component
# Generate and write out the header file
header_text = render_template(
jinja_env.get_template(file_basename + '.h.tmpl'), template_context)
header_path = posixpath.join(options.output_directory,
file_basename + '.h')
write_file(header_text, header_path)
# Generate and write out the implementation file
cpp_text = render_template(
jinja_env.get_template(file_basename + '.cc.tmpl'), template_context)
cpp_path = posixpath.join(options.output_directory, file_basename + '.cc')
write_file(cpp_text, cpp_path)
def main():
options = parse_options()
info_provider = create_component_info_provider(
os.path.normpath(options.info_dir), options.target_component)
idl_filenames = list(map(str.strip, open(options.idl_files_list)))
generate_origin_trial_features(info_provider, options, idl_filenames)
return 0
if __name__ == '__main__':
sys.exit(main())