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
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
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
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|.
53 'implements_interfaces': targets of 'implements' statements
54 'referenced_interfaces': reference interfaces that are introspected
55 (currently just targets of [PutForwards])
58 'ancestors': all ancestor interfaces
59 'inherited_extended_attributes': inherited extended attributes
60 (all controlling memory management)
63 'is_callback_interface': bool, callback interface or not
64 'implemented_as': value of [ImplementedAs=...] on interface (C++ class name)
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
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
84 from collections
import defaultdict
85 import cPickle
as pickle
89 from utilities
import idl_filename_to_component
, read_pickle_files
, write_pickle_file
, merge_dict_recursively
91 INHERITED_EXTENDED_ATTRIBUTES
= set([
95 'WillBeGarbageCollected',
98 # Main variable (filled in and exported)
101 # Auxiliary variables (not visible to future build steps)
102 partial_interface_files
= defaultdict(lambda: {
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."""
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
)
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
136 existing_value
= existing
[key
]
137 for inner_key
, inner_value
in value
.iteritems():
138 existing_value
[inner_key
].extend(inner_value
)
141 ################################################################################
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
]
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():
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(
222 for key
, value
in extended_attributes
.iteritems()
223 if key
in INHERITED_EXTENDED_ATTRIBUTES
)
224 parent
= interface_info
['parent']
226 parent_interfaces
[interface_name
] = parent
228 # Once all individual files handled, can compute inheritance information
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']
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
)
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
)
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 ################################################################################
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
,
332 options
.write_file_only_if_changed
)
335 if __name__
== '__main__':