Commit ae07325b authored by nbarth@chromium.org's avatar nbarth@chromium.org

Refactor interface dependency resolution

This refactors simplifies dependency resolution, and makes it easier to
extend.

Specifically:
* Move file handling into idl_reader
* Move actual definition and interface merging to methods on IdlDefinitions
  and IdlInterface (in idl_definitions)

This also clarifies exactly what's going on specially:
* we tack on some extended attributes to members
* we merge implemented interfaces to the main one

Other changes:
* use next() instead of looping over a one-element list
* remove useless exceptions (specific exception classes are only useful
  if we catch them, otherwise it's no better than a string)

This is to support a separate CL:
Bindings: inherit enums and callbacks from partial interface files.
https://codereview.chromium.org/203193015/

TBR=haraken
BUG=345137

Review URL: https://codereview.chromium.org/205873003

git-svn-id: svn://svn.chromium.org/blink/trunk@169613 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent c8104652
......@@ -148,6 +148,22 @@ class IdlDefinitions(object):
for interface in self.interfaces.itervalues():
interface.resolve_typedefs(typedefs)
def update(self, other):
"""Update with additional IdlDefinitions."""
for interface_name, new_interface in other.interfaces.iteritems():
if not new_interface.is_partial:
# Add as new interface
self.interfaces[interface_name] = new_interface
continue
# Merge partial to existing interface
try:
self.interfaces[interface_name].merge(new_interface)
except KeyError:
raise Exception('Tried to merge partial interface for {0}, '
'but no existing interface by that name'
.format(interface_name))
################################################################################
# Callback Functions
......@@ -241,6 +257,12 @@ class IdlInterface(object):
for operation in self.operations:
operation.resolve_typedefs(typedefs)
def merge(self, other):
"""Merge in another interface's members (e.g., partial interface)"""
self.attributes.extend(other.attributes)
self.constants.extend(other.constants)
self.operations.extend(other.operations)
class IdlException(IdlInterface):
# Properly exceptions and interfaces are distinct, and thus should inherit a
......
......@@ -60,18 +60,39 @@ class IdlReader(object):
definitions = self.read_idl_file(idl_filename)
if not self.interface_dependency_resolver:
return definitions
interface_name, _ = os.path.splitext(os.path.basename(idl_filename))
self.interface_dependency_resolver.resolve_dependencies(
definitions, interface_name)
self.interface_dependency_resolver.resolve_dependencies(definitions)
return definitions
def read_idl_file(self, idl_filename):
"""Returns an IdlDefinitions object for an IDL file, without any dependencies."""
"""Returns an IdlDefinitions object for an IDL file, without any dependencies.
The IdlDefinitions object is guaranteed to contain a single
IdlInterface; it may also contain other definitions, such as
callback functions and enumerations."""
ast = blink_idl_parser.parse_file(self.parser, idl_filename)
if not ast:
raise Exception('Failed to parse %s' % idl_filename)
definitions = IdlDefinitions(ast)
# Validate file contents with filename convention
# The Blink IDL filenaming convention is that the file
# <interface_name>.idl MUST contain exactly 1 interface (or exception),
# and the interface name must agree with the file's basename,
# unless it is a partial interface.
# (e.g., 'partial interface Foo' can be in FooBar.idl).
number_of_interfaces = len(definitions.interfaces)
if number_of_interfaces != 1:
raise Exception(
'Expected exactly 1 interface in file {0}, but found {1}'
.format(idl_filename, number_of_interfaces))
interface = next(definitions.interfaces.itervalues())
idl_file_basename, _ = os.path.splitext(os.path.basename(idl_filename))
if not interface.is_partial and interface.name != idl_file_basename:
raise Exception(
'Interface name "{0}" disagrees with IDL file basename "{1}".'
.format(interface.name, idl_file_basename))
# Validate extended attributes
if not self.extended_attribute_validator:
return definitions
......
......@@ -37,7 +37,6 @@ Design doc: http://www.chromium.org/developers/design-documents/idl-compiler#TOC
"""
import os.path
import cPickle as pickle
# The following extended attributes can be applied to a dependency interface,
# and are then applied to the individual members when merging.
......@@ -51,16 +50,6 @@ DEPENDENCY_EXTENDED_ATTRIBUTES = set([
])
class InterfaceNotFoundError(Exception):
"""Raised if (partial) interface not found in target IDL file."""
pass
class InvalidPartialInterfaceError(Exception):
"""Raised if a file listed as a partial interface is not in fact so."""
pass
class InterfaceDependencyResolver(object):
def __init__(self, interfaces_info, reader):
"""Initialize dependency resolver.
......@@ -74,74 +63,70 @@ class InterfaceDependencyResolver(object):
self.interfaces_info = interfaces_info
self.reader = reader
def resolve_dependencies(self, definitions, interface_name):
def resolve_dependencies(self, definitions):
"""Resolve dependencies, merging them into IDL definitions of main file.
Dependencies consist of 'partial interface' for the same interface as
in the main file, and other interfaces that this interface 'implements'.
These are merged into the main IdlInterface, as the main IdlInterface
implements all these members.
Referenced interfaces are added to IdlDefinitions, but not merged into
the main IdlInterface, as these are only referenced (their members are
introspected, but not implemented in this interface).
Inherited extended attributes are also added to the main IdlInterface.
Modifies definitions in place by adding parsed dependencies.
Args:
definitions: IdlDefinitions object, modified in place
interface_name:
name of interface whose dependencies are being resolved
"""
# The Blink IDL filenaming convention is that the file
# <interface_name>.idl MUST contain the interface "interface_name" or
# exception "interface_name", unless it is a dependency (e.g.,
# 'partial interface Foo' can be in FooBar.idl).
try:
target_interface = definitions.interfaces[interface_name]
except KeyError:
raise InterfaceNotFoundError('Could not find interface or exception "{0}" in {0}.idl'.format(interface_name))
if interface_name not in self.interfaces_info:
# No dependencies, nothing to do
return
target_interface = next(definitions.interfaces.itervalues())
interface_name = target_interface.name
interface_info = self.interfaces_info[interface_name]
if 'inherited_extended_attributes' in interface_info:
target_interface.extended_attributes.update(
interface_info['inherited_extended_attributes'])
merge_interface_dependencies(target_interface,
merge_interface_dependencies(definitions,
target_interface,
interface_info['dependencies_full_paths'],
self.reader)
for referenced_interface_name in interface_info['referenced_interfaces']:
referenced_definitions = self.reader.read_idl_definitions(
self.interfaces_info[referenced_interface_name]['full_path'])
definitions.interfaces.update(referenced_definitions.interfaces)
definitions.update(referenced_definitions)
def merge_interface_dependencies(target_interface, dependency_idl_filenames, reader):
def merge_interface_dependencies(definitions, target_interface, dependency_idl_filenames, reader):
"""Merge dependencies ('partial interface' and 'implements') in dependency_idl_filenames into target_interface.
No return: modifies target_interface in place.
"""
# Sort so order consistent, so can compare output from run to run.
for dependency_idl_filename in sorted(dependency_idl_filenames):
dependency_definitions = reader.read_idl_file(dependency_idl_filename)
dependency_interface = next(dependency_definitions.interfaces.itervalues())
dependency_interface_basename, _ = os.path.splitext(os.path.basename(dependency_idl_filename))
definitions = reader.read_idl_file(dependency_idl_filename)
for dependency_interface in definitions.interfaces.itervalues():
# Dependency files contain either partial interfaces for
# the (single) target interface, in which case the interface names
# must agree, or interfaces that are implemented by the target
# interface, in which case the interface names differ.
if (dependency_interface.is_partial and
dependency_interface.name != target_interface.name):
raise InvalidPartialInterfaceError('%s is not a partial interface of %s. There maybe a bug in the the dependency generator (compute_dependencies.py).' % (dependency_idl_filename, target_interface.name))
merge_dependency_interface(target_interface, dependency_interface, dependency_interface_basename)
transfer_extended_attributes(dependency_interface, dependency_interface_basename)
definitions.update(dependency_definitions)
if not dependency_interface.is_partial:
# Implemented interfaces (non-partial dependencies) are merged
# into the target interface, so Code Generator can just iterate
# over one list (and not need to handle 'implements' itself).
target_interface.merge(dependency_interface)
def merge_dependency_interface(target_interface, dependency_interface, dependency_interface_basename):
"""Merge dependency_interface into target_interface.
def transfer_extended_attributes(dependency_interface, dependency_interface_basename):
"""Transfer extended attributes from dependency interface onto members.
Merging consists of storing certain interface-level data in extended
attributes of the *members* (because there is no separate dependency
interface post-merging), then concatenating the lists.
interface post-merging).
The data storing consists of:
* applying certain extended attributes from the dependency interface
......@@ -149,7 +134,7 @@ def merge_dependency_interface(target_interface, dependency_interface, dependenc
* storing the C++ class of the implementation in an internal
extended attribute of each member, [ImplementedBy]
No return: modifies target_interface in place.
No return: modifies dependency_interface in place.
"""
merged_extended_attributes = dict(
(key, value)
......@@ -168,11 +153,9 @@ def merge_dependency_interface(target_interface, dependency_interface, dependenc
dependency_interface.extended_attributes.get(
'ImplementedAs', dependency_interface_basename))
def merge_lists(source_list, target_list):
for member in source_list:
member.extended_attributes.update(merged_extended_attributes)
target_list.extend(source_list)
merge_lists(dependency_interface.attributes, target_interface.attributes)
merge_lists(dependency_interface.constants, target_interface.constants)
merge_lists(dependency_interface.operations, target_interface.operations)
for attribute in dependency_interface.attributes:
attribute.extended_attributes.update(merged_extended_attributes)
for constant in dependency_interface.constants:
constant.extended_attributes.update(merged_extended_attributes)
for operation in dependency_interface.operations:
operation.extended_attributes.update(merged_extended_attributes)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment