diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/bindings/scripts/compute_interfaces_info_overall.py')
-rwxr-xr-x | chromium/third_party/WebKit/Source/bindings/scripts/compute_interfaces_info_overall.py | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/bindings/scripts/compute_interfaces_info_overall.py b/chromium/third_party/WebKit/Source/bindings/scripts/compute_interfaces_info_overall.py new file mode 100755 index 00000000000..658ccfade6f --- /dev/null +++ b/chromium/third_party/WebKit/Source/bindings/scripts/compute_interfaces_info_overall.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Compute global interface information, including public information, dependencies, and inheritance. + +Computed data is stored in a global variable, |interfaces_info|, and written as +output (concretely, exported as a pickle). This is then used by the IDL compiler +itself, so it does not need to compute global information itself, and so that +inter-IDL dependencies are clear, since they are all computed here. + +The |interfaces_info| pickle is a *global* dependency: any changes cause a full +rebuild. This is to avoid having to compute which public data is visible by +which IDL files on a file-by-file basis, which is very complex for little +benefit. +|interfaces_info| should thus only contain data about an interface that +contains paths or is needed by *other* interfaces, e.g., path data (to abstract +the compiler from OS-specific file paths) or public data (to avoid having to +read other interfaces unnecessarily). +It should *not* contain full information about an interface (e.g., all +extended attributes), as this would cause unnecessary rebuilds. + +|interfaces_info| is a dict, keyed by |interface_name|. + +Current keys are: +* dependencies: + 'implements_interfaces': targets of 'implements' statements + 'referenced_interfaces': reference interfaces that are introspected + (currently just targets of [PutForwards]) + +* inheritance: + 'ancestors': all ancestor interfaces + 'inherited_extended_attributes': inherited extended attributes + (all controlling memory management) + +* public: + 'is_callback_interface': bool, callback interface or not + 'implemented_as': value of [ImplementedAs=...] on interface (C++ class name) + +* paths: + 'full_path': path to the IDL file, so can lookup an IDL by interface name + 'include_path': path for use in C++ #include directives + 'dependencies_full_paths': paths to dependencies (for merging into main) + 'dependencies_include_paths': paths for use in C++ #include directives + +Note that all of these are stable information, unlikely to change without +moving or deleting files (hence requiring a full rebuild anyway) or significant +code changes (for inherited extended attributes). + +Design doc: http://www.chromium.org/developers/design-documents/idl-build +""" + +from collections import defaultdict +import cPickle as pickle +import optparse +import sys + +from utilities import read_pickle_files, write_pickle_file + +INHERITED_EXTENDED_ATTRIBUTES = set([ + 'ActiveDOMObject', + 'DependentLifetime', + 'GarbageCollected', + 'WillBeGarbageCollected', +]) + +# Main variable (filled in and exported) +interfaces_info = {} + +# Auxiliary variables (not visible to future build steps) +partial_interface_files = defaultdict(lambda: { + 'full_paths': [], + 'include_paths': [], +}) +parent_interfaces = {} +inherited_extended_attributes_by_interface = {} # interface name -> extended attributes + + +class IdlInterfaceFileNotFoundError(Exception): + """Raised if the IDL file implementing an interface cannot be found.""" + pass + + +def parse_options(): + usage = 'Usage: %prog [InfoIndividual.pickle]... [Info.pickle]' + parser = optparse.OptionParser(usage=usage) + parser.add_option('--write-file-only-if-changed', type='int', help='if true, do not write an output file if it would be identical to the existing one, which avoids unnecessary rebuilds in ninja') + + options, args = parser.parse_args() + if options.write_file_only_if_changed is None: + parser.error('Must specify whether file is only written if changed using --write-file-only-if-changed.') + options.write_file_only_if_changed = bool(options.write_file_only_if_changed) + return options, args + + +def dict_of_dicts_of_lists_update_or_append(existing, other): + """Updates an existing dict of dicts of lists, or appends to lists if key already present. + + Needed for merging partial_interface_files across components. + """ + for key, value in other.iteritems(): + if key not in existing: + existing[key] = value + continue + existing_value = existing[key] + for inner_key, inner_value in value.iteritems(): + existing_value[inner_key].extend(inner_value) + + +################################################################################ +# Computations +################################################################################ + +def compute_inheritance_info(interface_name): + """Compute inheritance information, namely ancestors and inherited extended attributes.""" + def generate_ancestors(interface_name): + while interface_name in parent_interfaces: + interface_name = parent_interfaces[interface_name] + yield interface_name + + ancestors = list(generate_ancestors(interface_name)) + inherited_extended_attributes = inherited_extended_attributes_by_interface[interface_name] + for ancestor in ancestors: + # Ancestors may not be present, notably if an ancestor is a generated + # IDL file and we are running this script from run-bindings-tests, + # where we don't generate these files. + ancestor_extended_attributes = inherited_extended_attributes_by_interface.get(ancestor, {}) + inherited_extended_attributes.update(ancestor_extended_attributes) + + interfaces_info[interface_name].update({ + 'ancestors': ancestors, + 'inherited_extended_attributes': inherited_extended_attributes, + }) + + +def compute_interfaces_info_overall(info_individuals): + """Compute information about IDL files. + + Information is stored in global interfaces_info. + """ + for info in info_individuals: + # No overlap between interface names, so ok to use dict.update + interfaces_info.update(info['interfaces_info']) + # Interfaces in one component may have partial interfaces in + # another component. This is ok (not a layering violation), since + # partial interfaces are used to *extend* interfaces. + # We thus need to update or append if already present + dict_of_dicts_of_lists_update_or_append( + partial_interface_files, info['partial_interface_files']) + + # Record inheritance information individually + for interface_name, interface_info in interfaces_info.iteritems(): + extended_attributes = interface_info['extended_attributes'] + inherited_extended_attributes_by_interface[interface_name] = dict( + (key, value) + for key, value in extended_attributes.iteritems() + if key in INHERITED_EXTENDED_ATTRIBUTES) + parent = interface_info['parent'] + if parent: + parent_interfaces[interface_name] = parent + + # Once all individual files handled, can compute inheritance information + # and dependencies + + # Compute inheritance info + for interface_name in interfaces_info: + compute_inheritance_info(interface_name) + + # Compute dependencies + # Move implements info from implement*ed* interface (rhs of 'implements') + # to implement*ing* interface (lhs of 'implements'). + # Note that moving an 'implements' statement between implementing and + # implemented files does not change the info (or hence cause a rebuild)! + for right_interface_name, interface_info in interfaces_info.iteritems(): + for left_interface_name in interface_info['implemented_by_interfaces']: + interfaces_info[left_interface_name]['implements_interfaces'].append(right_interface_name) + del interface_info['implemented_by_interfaces'] + + # An IDL file's dependencies are partial interface files that extend it, + # and files for other interfaces that this interfaces implements. + for interface_name, interface_info in interfaces_info.iteritems(): + partial_interface_paths = partial_interface_files[interface_name] + partial_interfaces_full_paths = partial_interface_paths['full_paths'] + # Partial interface definitions each need an include, as they are + # implemented in separate classes from the main interface. + partial_interfaces_include_paths = partial_interface_paths['include_paths'] + + implemented_interfaces = interface_info['implements_interfaces'] + try: + implemented_interfaces_info = [ + interfaces_info[interface] + for interface in implemented_interfaces] + except KeyError as key_name: + raise IdlInterfaceFileNotFoundError('Could not find the IDL file where the following implemented interface is defined: %s' % key_name) + implemented_interfaces_full_paths = [ + implemented_interface_info['full_path'] + for implemented_interface_info in implemented_interfaces_info] + # Implemented interfaces don't need includes, as this is handled in + # the Blink implementation (they are implemented on |impl| itself, + # hence header is included in implementing class). + # However, they are needed for legacy implemented interfaces that + # are being treated as partial interfaces, until we remove these. + # http://crbug.com/360435 + implemented_interfaces_include_paths = [ + implemented_interface_info['include_path'] + for implemented_interface_info in implemented_interfaces_info + if implemented_interface_info['is_legacy_treat_as_partial_interface']] + + interface_info.update({ + 'dependencies_full_paths': (partial_interfaces_full_paths + + implemented_interfaces_full_paths), + 'dependencies_include_paths': (partial_interfaces_include_paths + + implemented_interfaces_include_paths), + }) + + # Clean up temporary private information + for interface_info in interfaces_info.itervalues(): + del interface_info['extended_attributes'] + del interface_info['is_legacy_treat_as_partial_interface'] + del interface_info['parent'] + + +################################################################################ + +def main(): + options, args = parse_options() + # args = Input1, Input2, ..., Output + interfaces_info_filename = args.pop() + info_individuals = read_pickle_files(args) + + compute_interfaces_info_overall(info_individuals) + write_pickle_file(interfaces_info_filename, + interfaces_info, + options.write_file_only_if_changed) + + +if __name__ == '__main__': + sys.exit(main()) |