OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # |
| 3 # Copyright (C) 2013 Google Inc. All rights reserved. |
| 4 # |
| 5 # Redistribution and use in source and binary forms, with or without |
| 6 # modification, are permitted provided that the following conditions are |
| 7 # met: |
| 8 # |
| 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. |
| 18 # |
| 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. |
| 30 |
| 31 """Compute global interface information, including public information, dependenc
ies, and inheritance. |
| 32 |
| 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. |
| 37 |
| 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. |
| 48 |
| 49 |interfaces_info| is a dict, keyed by |interface_name|. |
| 50 |
| 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]) |
| 56 |
| 57 * inheritance: |
| 58 'ancestors': all ancestor interfaces |
| 59 'inherited_extended_attributes': inherited extended attributes |
| 60 (all controlling memory management) |
| 61 |
| 62 * public: |
| 63 'is_callback_interface': bool, callback interface or not |
| 64 'implemented_as': value of [ImplementedAs=...] on interface (C++ class name) |
| 65 |
| 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 |
| 72 Note that all of these are stable information, unlikely to change without |
| 73 moving or deleting files (hence requiring a full rebuild anyway) or significant |
| 74 code changes (for inherited extended attributes). |
| 75 |
| 76 Design doc: http://www.chromium.org/developers/design-documents/idl-build |
| 77 """ |
| 78 |
| 79 from collections import defaultdict |
| 80 import cPickle as pickle |
| 81 import optparse |
| 82 import sys |
| 83 |
| 84 from utilities import read_pickle_files, write_pickle_file |
| 85 |
| 86 INHERITED_EXTENDED_ATTRIBUTES = set([ |
| 87 'ActiveDOMObject', |
| 88 'DependentLifetime', |
| 89 'GarbageCollected', |
| 90 'WillBeGarbageCollected', |
| 91 ]) |
| 92 |
| 93 # Main variable (filled in and exported) |
| 94 interfaces_info = {} |
| 95 |
| 96 # Auxiliary variables (not visible to future build steps) |
| 97 partial_interface_files = defaultdict(lambda: { |
| 98 'full_paths': [], |
| 99 'include_paths': [], |
| 100 }) |
| 101 parent_interfaces = {} |
| 102 inherited_extended_attributes_by_interface = {} # interface name -> extended at
tributes |
| 103 |
| 104 |
| 105 class IdlInterfaceFileNotFoundError(Exception): |
| 106 """Raised if the IDL file implementing an interface cannot be found.""" |
| 107 pass |
| 108 |
| 109 |
| 110 def parse_options(): |
| 111 usage = 'Usage: %prog [InfoIndividual.pickle]... [Info.pickle]' |
| 112 parser = optparse.OptionParser(usage=usage) |
| 113 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') |
| 114 |
| 115 options, args = parser.parse_args() |
| 116 if options.write_file_only_if_changed is None: |
| 117 parser.error('Must specify whether file is only written if changed using
--write-file-only-if-changed.') |
| 118 options.write_file_only_if_changed = bool(options.write_file_only_if_changed
) |
| 119 return options, args |
| 120 |
| 121 |
| 122 def dict_of_dicts_of_lists_update_or_append(existing, other): |
| 123 """Updates an existing dict of dicts of lists, or appends to lists if key al
ready present. |
| 124 |
| 125 Needed for merging partial_interface_files across components. |
| 126 """ |
| 127 for key, value in other.iteritems(): |
| 128 if key not in existing: |
| 129 existing[key] = value |
| 130 continue |
| 131 existing_value = existing[key] |
| 132 for inner_key, inner_value in value.iteritems(): |
| 133 existing_value[inner_key].extend(inner_value) |
| 134 |
| 135 |
| 136 ################################################################################ |
| 137 # Computations |
| 138 ################################################################################ |
| 139 |
| 140 def compute_inheritance_info(interface_name): |
| 141 """Compute inheritance information, namely ancestors and inherited extended
attributes.""" |
| 142 def generate_ancestors(interface_name): |
| 143 while interface_name in parent_interfaces: |
| 144 interface_name = parent_interfaces[interface_name] |
| 145 yield interface_name |
| 146 |
| 147 ancestors = list(generate_ancestors(interface_name)) |
| 148 inherited_extended_attributes = inherited_extended_attributes_by_interface[i
nterface_name] |
| 149 for ancestor in ancestors: |
| 150 # Ancestors may not be present, notably if an ancestor is a generated |
| 151 # IDL file and we are running this script from run-bindings-tests, |
| 152 # where we don't generate these files. |
| 153 ancestor_extended_attributes = inherited_extended_attributes_by_interfac
e.get(ancestor, {}) |
| 154 inherited_extended_attributes.update(ancestor_extended_attributes) |
| 155 |
| 156 interfaces_info[interface_name].update({ |
| 157 'ancestors': ancestors, |
| 158 'inherited_extended_attributes': inherited_extended_attributes, |
| 159 }) |
| 160 |
| 161 |
| 162 def compute_interfaces_info_overall(info_individuals): |
| 163 """Compute information about IDL files. |
| 164 |
| 165 Information is stored in global interfaces_info. |
| 166 """ |
| 167 for info in info_individuals: |
| 168 # No overlap between interface names, so ok to use dict.update |
| 169 interfaces_info.update(info['interfaces_info']) |
| 170 # Interfaces in one component may have partial interfaces in |
| 171 # another component. This is ok (not a layering violation), since |
| 172 # partial interfaces are used to *extend* interfaces. |
| 173 # We thus need to update or append if already present |
| 174 dict_of_dicts_of_lists_update_or_append( |
| 175 partial_interface_files, info['partial_interface_files']) |
| 176 |
| 177 # Record inheritance information individually |
| 178 for interface_name, interface_info in interfaces_info.iteritems(): |
| 179 extended_attributes = interface_info['extended_attributes'] |
| 180 inherited_extended_attributes_by_interface[interface_name] = dict( |
| 181 (key, value) |
| 182 for key, value in extended_attributes.iteritems() |
| 183 if key in INHERITED_EXTENDED_ATTRIBUTES) |
| 184 parent = interface_info['parent'] |
| 185 if parent: |
| 186 parent_interfaces[interface_name] = parent |
| 187 |
| 188 # Once all individual files handled, can compute inheritance information |
| 189 # and dependencies |
| 190 |
| 191 # Compute inheritance info |
| 192 for interface_name in interfaces_info: |
| 193 compute_inheritance_info(interface_name) |
| 194 |
| 195 # Compute dependencies |
| 196 # Move implements info from implement*ed* interface (rhs of 'implements') |
| 197 # to implement*ing* interface (lhs of 'implements'). |
| 198 # Note that moving an 'implements' statement between implementing and |
| 199 # implemented files does not change the info (or hence cause a rebuild)! |
| 200 for right_interface_name, interface_info in interfaces_info.iteritems(): |
| 201 for left_interface_name in interface_info['implemented_by_interfaces']: |
| 202 interfaces_info[left_interface_name]['implements_interfaces'].append
(right_interface_name) |
| 203 del interface_info['implemented_by_interfaces'] |
| 204 |
| 205 # An IDL file's dependencies are partial interface files that extend it, |
| 206 # and files for other interfaces that this interfaces implements. |
| 207 for interface_name, interface_info in interfaces_info.iteritems(): |
| 208 partial_interface_paths = partial_interface_files[interface_name] |
| 209 partial_interfaces_full_paths = partial_interface_paths['full_paths'] |
| 210 # Partial interface definitions each need an include, as they are |
| 211 # implemented in separate classes from the main interface. |
| 212 partial_interfaces_include_paths = partial_interface_paths['include_path
s'] |
| 213 |
| 214 implemented_interfaces = interface_info['implements_interfaces'] |
| 215 try: |
| 216 implemented_interfaces_info = [ |
| 217 interfaces_info[interface] |
| 218 for interface in implemented_interfaces] |
| 219 except KeyError as key_name: |
| 220 raise IdlInterfaceFileNotFoundError('Could not find the IDL file whe
re the following implemented interface is defined: %s' % key_name) |
| 221 implemented_interfaces_full_paths = [ |
| 222 implemented_interface_info['full_path'] |
| 223 for implemented_interface_info in implemented_interfaces_info] |
| 224 # Implemented interfaces don't need includes, as this is handled in |
| 225 # the Blink implementation (they are implemented on |impl| itself, |
| 226 # hence header is included in implementing class). |
| 227 # However, they are needed for legacy implemented interfaces that |
| 228 # are being treated as partial interfaces, until we remove these. |
| 229 # http://crbug.com/360435 |
| 230 implemented_interfaces_include_paths = [ |
| 231 implemented_interface_info['include_path'] |
| 232 for implemented_interface_info in implemented_interfaces_info |
| 233 if implemented_interface_info['is_legacy_treat_as_partial_interface'
]] |
| 234 |
| 235 interface_info.update({ |
| 236 'dependencies_full_paths': (partial_interfaces_full_paths + |
| 237 implemented_interfaces_full_paths), |
| 238 'dependencies_include_paths': (partial_interfaces_include_paths + |
| 239 implemented_interfaces_include_paths)
, |
| 240 }) |
| 241 |
| 242 # Clean up temporary private information |
| 243 for interface_info in interfaces_info.itervalues(): |
| 244 del interface_info['extended_attributes'] |
| 245 del interface_info['is_legacy_treat_as_partial_interface'] |
| 246 del interface_info['parent'] |
| 247 |
| 248 |
| 249 ################################################################################ |
| 250 |
| 251 def main(): |
| 252 options, args = parse_options() |
| 253 # args = Input1, Input2, ..., Output |
| 254 interfaces_info_filename = args.pop() |
| 255 info_individuals = read_pickle_files(args) |
| 256 |
| 257 compute_interfaces_info_overall(info_individuals) |
| 258 write_pickle_file(interfaces_info_filename, |
| 259 interfaces_info, |
| 260 options.write_file_only_if_changed) |
| 261 |
| 262 |
| 263 if __name__ == '__main__': |
| 264 sys.exit(main()) |
OLD | NEW |