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, dependencies, and inheritance. 32 33Computed data is stored in a global variable, |interfaces_info|, and written as 34output (concretely, exported as a pickle). This is then used by the IDL compiler 35itself, so it does not need to compute global information itself, and so that 36inter-IDL dependencies are clear, since they are all computed here. 37 38The |interfaces_info| pickle is a *global* dependency: any changes cause a full 39rebuild. This is to avoid having to compute which public data is visible by 40which IDL files on a file-by-file basis, which is very complex for little 41benefit. 42|interfaces_info| should thus only contain data about an interface that 43contains paths or is needed by *other* interfaces, e.g., path data (to abstract 44the compiler from OS-specific file paths) or public data (to avoid having to 45read other interfaces unnecessarily). 46It should *not* contain full information about an interface (e.g., all 47extended attributes), as this would cause unnecessary rebuilds. 48 49|interfaces_info| is a dict, keyed by |interface_name|. 50 51Current 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 72Note that all of these are stable information, unlikely to change without 73moving or deleting files (hence requiring a full rebuild anyway) or significant 74code changes (for inherited extended attributes). 75 76Design doc: http://www.chromium.org/developers/design-documents/idl-build 77""" 78 79from collections import defaultdict 80import cPickle as pickle 81import optparse 82import sys 83 84from utilities import read_pickle_files, write_pickle_file 85 86INHERITED_EXTENDED_ATTRIBUTES = set([ 87 'ActiveDOMObject', 88 'DependentLifetime', 89 'GarbageCollected', 90 'WillBeGarbageCollected', 91]) 92 93# Main variable (filled in and exported) 94interfaces_info = {} 95 96# Auxiliary variables (not visible to future build steps) 97partial_interface_files = defaultdict(lambda: { 98 'full_paths': [], 99 'include_paths': [], 100}) 101parent_interfaces = {} 102inherited_extended_attributes_by_interface = {} # interface name -> extended attributes 103 104 105class IdlInterfaceFileNotFoundError(Exception): 106 """Raised if the IDL file implementing an interface cannot be found.""" 107 pass 108 109 110def 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 122def 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 already 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 140def 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[interface_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_interface.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 162def 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_paths'] 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 where 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 251def 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 263if __name__ == '__main__': 264 sys.exit(main()) 265