Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / bindings / scripts / compute_interfaces_info_overall.py
blob380510dda6077d4b5f6effc809e4396039bd0e02
1 #!/usr/bin/python
3 # Copyright (C) 2013 Google Inc. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
14 # distribution.
15 # * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 """Compute global interface information, including public information, dependencies, and inheritance.
33 Computed data is stored in a global variable, |interfaces_info|, and written as
34 output (concretely, exported as a pickle). This is then used by the IDL compiler
35 itself, so it does not need to compute global information itself, and so that
36 inter-IDL dependencies are clear, since they are all computed here.
38 The |interfaces_info| pickle is a *global* dependency: any changes cause a full
39 rebuild. This is to avoid having to compute which public data is visible by
40 which IDL files on a file-by-file basis, which is very complex for little
41 benefit.
42 |interfaces_info| should thus only contain data about an interface that
43 contains paths or is needed by *other* interfaces, e.g., path data (to abstract
44 the compiler from OS-specific file paths) or public data (to avoid having to
45 read other interfaces unnecessarily).
46 It should *not* contain full information about an interface (e.g., all
47 extended attributes), as this would cause unnecessary rebuilds.
49 |interfaces_info| is a dict, keyed by |interface_name|.
51 Current keys are:
52 * dependencies:
53 'implements_interfaces': targets of 'implements' statements
54 'referenced_interfaces': reference interfaces that are introspected
55 (currently just targets of [PutForwards])
57 * inheritance:
58 'ancestors': all ancestor interfaces
59 'inherited_extended_attributes': inherited extended attributes
60 (all controlling memory management)
62 * public:
63 'is_callback_interface': bool, callback interface or not
64 'implemented_as': value of [ImplementedAs=...] on interface (C++ class name)
66 * paths:
67 'full_path': path to the IDL file, so can lookup an IDL by interface name
68 'include_path': path for use in C++ #include directives
69 'dependencies_full_paths': paths to dependencies (for merging into main)
70 'dependencies_include_paths': paths for use in C++ #include directives
71 'dependencies_other_component_full_paths':
72 paths to dependencies (cannot merge because of other component)
73 'dependencies_other_component_include_paths':
74 paths for use in C++ #include directives because of dependencies in
75 other component
77 Note that all of these are stable information, unlikely to change without
78 moving or deleting files (hence requiring a full rebuild anyway) or significant
79 code changes (for inherited extended attributes).
81 Design doc: http://www.chromium.org/developers/design-documents/idl-build
82 """
84 from collections import defaultdict
85 import cPickle as pickle
86 import optparse
87 import sys
89 from utilities import idl_filename_to_component, read_pickle_files, write_pickle_file, merge_dict_recursively
91 INHERITED_EXTENDED_ATTRIBUTES = set([
92 'ActiveDOMObject',
93 'DependentLifetime',
94 'GarbageCollected',
95 'WillBeGarbageCollected',
98 # Main variable (filled in and exported)
99 interfaces_info = {}
101 # Auxiliary variables (not visible to future build steps)
102 partial_interface_files = defaultdict(lambda: {
103 'full_paths': [],
104 'include_paths': [],
106 parent_interfaces = {}
107 inherited_extended_attributes_by_interface = {} # interface name -> extended attributes
110 class IdlInterfaceFileNotFoundError(Exception):
111 """Raised if the IDL file implementing an interface cannot be found."""
112 pass
115 def parse_options():
116 usage = 'Usage: %prog [InfoIndividual.pickle]... [Info.pickle]'
117 parser = optparse.OptionParser(usage=usage)
118 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')
120 options, args = parser.parse_args()
121 if options.write_file_only_if_changed is None:
122 parser.error('Must specify whether file is only written if changed using --write-file-only-if-changed.')
123 options.write_file_only_if_changed = bool(options.write_file_only_if_changed)
124 return options, args
127 def dict_of_dicts_of_lists_update_or_append(existing, other):
128 """Updates an existing dict of dicts of lists, or appends to lists if key already present.
130 Needed for merging partial_interface_files across components.
132 for key, value in other.iteritems():
133 if key not in existing:
134 existing[key] = value
135 continue
136 existing_value = existing[key]
137 for inner_key, inner_value in value.iteritems():
138 existing_value[inner_key].extend(inner_value)
141 ################################################################################
142 # Computations
143 ################################################################################
145 def compute_inheritance_info(interface_name):
146 """Compute inheritance information, namely ancestors and inherited extended attributes."""
147 def generate_ancestors(interface_name):
148 while interface_name in parent_interfaces:
149 interface_name = parent_interfaces[interface_name]
150 yield interface_name
152 ancestors = list(generate_ancestors(interface_name))
153 inherited_extended_attributes = inherited_extended_attributes_by_interface[interface_name]
154 for ancestor in ancestors:
155 # Ancestors may not be present, notably if an ancestor is a generated
156 # IDL file and we are running this script from run-bindings-tests,
157 # where we don't generate these files.
158 ancestor_extended_attributes = inherited_extended_attributes_by_interface.get(ancestor, {})
159 inherited_extended_attributes.update(ancestor_extended_attributes)
161 interfaces_info[interface_name].update({
162 'ancestors': ancestors,
163 'inherited_extended_attributes': inherited_extended_attributes,
167 def compute_global_type_info():
168 ancestors = {}
169 dictionaries = {}
170 component_dirs = {}
171 implemented_as_interfaces = {}
172 will_be_garbage_collected_interfaces = set()
173 garbage_collected_interfaces = set()
174 callback_interfaces = set()
176 for interface_name, interface_info in interfaces_info.iteritems():
177 component_dirs[interface_name] = idl_filename_to_component(interface_info['full_path'])
179 if interface_info['ancestors']:
180 ancestors[interface_name] = interface_info['ancestors']
181 if interface_info['is_callback_interface']:
182 callback_interfaces.add(interface_name)
183 if interface_info['is_dictionary']:
184 dictionaries[interface_name] = interface_info['is_dictionary']
185 if interface_info['implemented_as']:
186 implemented_as_interfaces[interface_name] = interface_info['implemented_as']
188 inherited_extended_attributes = interface_info['inherited_extended_attributes']
189 if 'WillBeGarbageCollected' in inherited_extended_attributes:
190 will_be_garbage_collected_interfaces.add(interface_name)
191 if 'GarbageCollected' in inherited_extended_attributes:
192 garbage_collected_interfaces.add(interface_name)
194 interfaces_info['ancestors'] = ancestors
195 interfaces_info['callback_interfaces'] = callback_interfaces
196 interfaces_info['dictionaries'] = dictionaries
197 interfaces_info['implemented_as_interfaces'] = implemented_as_interfaces
198 interfaces_info['garbage_collected_interfaces'] = garbage_collected_interfaces
199 interfaces_info['will_be_garbage_collected_interfaces'] = will_be_garbage_collected_interfaces
200 interfaces_info['component_dirs'] = component_dirs
203 def compute_interfaces_info_overall(info_individuals):
204 """Compute information about IDL files.
206 Information is stored in global interfaces_info.
208 for info in info_individuals:
209 merge_dict_recursively(interfaces_info, info['interfaces_info'])
210 # Interfaces in one component may have partial interfaces in
211 # another component. This is ok (not a layering violation), since
212 # partial interfaces are used to *extend* interfaces.
213 # We thus need to update or append if already present
214 dict_of_dicts_of_lists_update_or_append(
215 partial_interface_files, info['partial_interface_files'])
217 # Record inheritance information individually
218 for interface_name, interface_info in interfaces_info.iteritems():
219 extended_attributes = interface_info['extended_attributes']
220 inherited_extended_attributes_by_interface[interface_name] = dict(
221 (key, value)
222 for key, value in extended_attributes.iteritems()
223 if key in INHERITED_EXTENDED_ATTRIBUTES)
224 parent = interface_info['parent']
225 if parent:
226 parent_interfaces[interface_name] = parent
228 # Once all individual files handled, can compute inheritance information
229 # and dependencies
231 # Compute inheritance info
232 for interface_name in interfaces_info:
233 compute_inheritance_info(interface_name)
235 # Compute dependencies
236 # Move implements info from implement*ed* interface (rhs of 'implements')
237 # to implement*ing* interface (lhs of 'implements').
238 # Note that moving an 'implements' statement between implementing and
239 # implemented files does not change the info (or hence cause a rebuild)!
240 for right_interface_name, interface_info in interfaces_info.iteritems():
241 for left_interface_name in interface_info['implemented_by_interfaces']:
242 interfaces_info[left_interface_name]['implements_interfaces'].append(right_interface_name)
243 del interface_info['implemented_by_interfaces']
245 # An IDL file's dependencies are partial interface files that extend it,
246 # and files for other interfaces that this interfaces implements.
247 for interface_name, interface_info in interfaces_info.iteritems():
248 partial_interface_paths = partial_interface_files[interface_name]
249 partial_interfaces_full_paths = partial_interface_paths['full_paths']
250 # Partial interface definitions each need an include, as they are
251 # implemented in separate classes from the main interface.
252 partial_interfaces_include_paths = partial_interface_paths['include_paths']
254 implemented_interfaces = interface_info['implements_interfaces']
255 try:
256 implemented_interfaces_info = [
257 interfaces_info[interface]
258 for interface in implemented_interfaces]
259 except KeyError as key_name:
260 raise IdlInterfaceFileNotFoundError('Could not find the IDL file where the following implemented interface is defined: %s' % key_name)
261 implemented_interfaces_full_paths = [
262 implemented_interface_info['full_path']
263 for implemented_interface_info in implemented_interfaces_info]
264 # Implemented interfaces don't need includes, as this is handled in
265 # the Blink implementation (they are implemented on |impl| itself,
266 # hence header is included in implementing class).
267 # However, they are needed for legacy implemented interfaces that
268 # are being treated as partial interfaces, until we remove these.
269 # http://crbug.com/360435
270 implemented_interfaces_include_paths = []
271 for implemented_interface_info in implemented_interfaces_info:
272 if (implemented_interface_info['is_legacy_treat_as_partial_interface'] and
273 implemented_interface_info['include_path']):
274 implemented_interfaces_include_paths.append(
275 implemented_interface_info['include_path'])
277 dependencies_full_paths = implemented_interfaces_full_paths
278 dependencies_include_paths = implemented_interfaces_include_paths
279 dependencies_other_component_full_paths = []
280 dependencies_other_component_include_paths = []
282 component = idl_filename_to_component(interface_info['full_path'])
283 for full_path in partial_interfaces_full_paths:
284 partial_interface_component = idl_filename_to_component(full_path)
285 if component == partial_interface_component:
286 dependencies_full_paths.append(full_path)
287 else:
288 dependencies_other_component_full_paths.append(full_path)
290 for include_path in partial_interfaces_include_paths:
291 partial_interface_component = idl_filename_to_component(include_path)
292 if component == partial_interface_component:
293 dependencies_include_paths.append(include_path)
294 else:
295 dependencies_other_component_include_paths.append(include_path)
297 if interface_info['has_union_types']:
298 dependencies_include_paths.append(
299 'bindings/%s/v8/UnionTypes%s.h' % (component, component.capitalize()))
301 interface_info.update({
302 'dependencies_full_paths': dependencies_full_paths,
303 'dependencies_include_paths': dependencies_include_paths,
304 'dependencies_other_component_full_paths':
305 dependencies_other_component_full_paths,
306 'dependencies_other_component_include_paths':
307 dependencies_other_component_include_paths,
310 # Clean up temporary private information
311 for interface_info in interfaces_info.itervalues():
312 del interface_info['extended_attributes']
313 del interface_info['has_union_types']
314 del interface_info['is_legacy_treat_as_partial_interface']
316 # Compute global_type_info to interfaces_info so that idl_compiler does
317 # not need to always calculate the info in __init__.
318 compute_global_type_info()
321 ################################################################################
323 def main():
324 options, args = parse_options()
325 # args = Input1, Input2, ..., Output
326 interfaces_info_filename = args.pop()
327 info_individuals = read_pickle_files(args)
329 compute_interfaces_info_overall(info_individuals)
330 write_pickle_file(interfaces_info_filename,
331 interfaces_info,
332 options.write_file_only_if_changed)
335 if __name__ == '__main__':
336 sys.exit(main())