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 31import optparse 32import os 33import posixpath 34import re 35import string 36 37 38class IdlBadFilenameError(Exception): 39 """Raised if an IDL filename disagrees with the interface name in the file.""" 40 pass 41 42 43class IdlInterfaceFileNotFoundError(Exception): 44 """Raised if the IDL file implementing an interface cannot be found.""" 45 pass 46 47 48def parse_options(): 49 parser = optparse.OptionParser() 50 parser.add_option('--event-names-file', help='output file') 51 parser.add_option('--main-idl-files-list', help='file listing main (compiled to Blink) IDL files') 52 parser.add_option('--support-idl-files-list', help='file listing support IDL files (not compiled to Blink, e.g. testing)') 53 parser.add_option('--interface-dependencies-file', help='output file') 54 parser.add_option('--bindings-derived-sources-file', help='output file') 55 parser.add_option('--window-constructors-file', help='output file') 56 parser.add_option('--workerglobalscope-constructors-file', help='output file') 57 parser.add_option('--sharedworkerglobalscope-constructors-file', help='output file') 58 parser.add_option('--dedicatedworkerglobalscope-constructors-file', help='output file') 59 parser.add_option('--serviceworkerglobalscope-constructors-file', help='output file') 60 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') 61 options, args = parser.parse_args() 62 if options.event_names_file is None: 63 parser.error('Must specify an output file using --event-names-file.') 64 if options.interface_dependencies_file is None: 65 parser.error('Must specify an output file using --interface-dependencies-file.') 66 if options.bindings_derived_sources_file is None: 67 parser.error('Must specify an output file using --bindings-derived-sources-file.') 68 if options.window_constructors_file is None: 69 parser.error('Must specify an output file using --window-constructors-file.') 70 if options.workerglobalscope_constructors_file is None: 71 parser.error('Must specify an output file using --workerglobalscope-constructors-file.') 72 if options.sharedworkerglobalscope_constructors_file is None: 73 parser.error('Must specify an output file using --sharedworkerglobalscope-constructors-file.') 74 if options.dedicatedworkerglobalscope_constructors_file is None: 75 parser.error('Must specify an output file using --dedicatedworkerglobalscope-constructors-file.') 76 if options.serviceworkerglobalscope_constructors_file is None: 77 parser.error('Must specify an output file using --serviceworkerglobalscope-constructors-file.') 78 if options.main_idl_files_list is None: 79 parser.error('Must specify a file listing main IDL files using --main-idl-files-list.') 80 if options.support_idl_files_list is None: 81 parser.error('Must specify a file listing support IDL files using --support-idl-files-list.') 82 if options.write_file_only_if_changed is None: 83 parser.error('Must specify whether file is only written if changed using --write-file-only-if-changed.') 84 options.write_file_only_if_changed = bool(options.write_file_only_if_changed) 85 if args: 86 parser.error('No arguments taken, but "%s" given.' % ' '.join(args)) 87 return options 88 89 90def get_file_contents(idl_filename): 91 with open(idl_filename) as idl_file: 92 lines = idl_file.readlines() 93 return ''.join(lines) 94 95 96def write_file(new_lines, destination_filename, only_if_changed): 97 if only_if_changed and os.path.isfile(destination_filename): 98 with open(destination_filename) as destination_file: 99 old_lines = destination_file.readlines() 100 if old_lines == new_lines: 101 return 102 with open(destination_filename, 'w') as destination_file: 103 destination_file.write(''.join(new_lines)) 104 105 106def get_partial_interface_name_from_idl(file_contents): 107 match = re.search(r'partial\s+interface\s+(\w+)', file_contents) 108 return match and match.group(1) 109 110 111# identifier-A implements identifier-B; 112# http://www.w3.org/TR/WebIDL/#idl-implements-statements 113def get_implemented_interfaces_from_idl(file_contents, interface_name): 114 def get_implemented(left_identifier, right_identifier): 115 # identifier-A must be the current interface 116 if left_identifier != interface_name: 117 raise IdlBadFilenameError("Identifier on the left of the 'implements' statement should be %s in %s.idl, but found %s" % (interface_name, interface_name, left_identifier)) 118 return right_identifier 119 120 implements_re = r'^\s*(\w+)\s+implements\s+(\w+)\s*;' 121 implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE) 122 implements_pairs = [(match.group(1), match.group(2)) 123 for match in implements_matches] 124 return [get_implemented(left, right) for left, right in implements_pairs] 125 126 127def is_callback_interface_from_idl(file_contents): 128 match = re.search(r'callback\s+interface\s+\w+', file_contents) 129 return bool(match) 130 131 132def get_parent_interface(file_contents): 133 match = re.search(r'interface\s+\w+\s*:\s*(\w+)\s*', file_contents) 134 return match and match.group(1) 135 136 137def get_interface_extended_attributes_from_idl(file_contents): 138 match = re.search(r'\[(.*)\]\s+(callback\s+)?(interface|exception)\s+(\w+)', 139 file_contents, flags=re.DOTALL) 140 if not match: 141 return {} 142 # Strip comments 143 # re.compile needed b/c Python 2.6 doesn't support flags in re.sub 144 single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE) 145 block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL) 146 extended_attributes_string = re.sub(single_line_comment_re, '', match.group(1)) 147 extended_attributes_string = re.sub(block_comment_re, '', extended_attributes_string) 148 extended_attributes = {} 149 # FIXME: this splitting is WRONG: it fails on ExtendedAttributeArgList like 150 # 'NamedConstructor=Foo(a, b)' 151 parts = [extended_attribute.strip() 152 for extended_attribute in extended_attributes_string.split(',') 153 # Discard empty parts, which may exist due to trailing comma 154 if extended_attribute.strip()] 155 for part in parts: 156 name, _, value = map(string.strip, part.partition('=')) 157 extended_attributes[name] = value 158 return extended_attributes 159 160 161def generate_constructor_attribute_list(interface_name, extended_attributes): 162 extended_attributes_list = [ 163 name + '=' + extended_attributes[name] 164 for name in 'Conditional', 'PerContextEnabled', 'RuntimeEnabled' 165 if name in extended_attributes] 166 if extended_attributes_list: 167 extended_string = '[%s] ' % ', '.join(extended_attributes_list) 168 else: 169 extended_string = '' 170 171 attribute_string = 'attribute {interface_name}Constructor {interface_name}'.format(interface_name=interface_name) 172 attributes_list = [extended_string + attribute_string] 173 174 # In addition to the regular property, for every [NamedConstructor] 175 # extended attribute on an interface, a corresponding property MUST exist 176 # on the ECMAScript global object. 177 if 'NamedConstructor' in extended_attributes: 178 named_constructor = extended_attributes['NamedConstructor'] 179 # Extract function name, namely everything before opening '(' 180 constructor_name = re.sub(r'\(.*', '', named_constructor) 181 # Note the reduplicated 'ConstructorConstructor' 182 attribute_string = 'attribute %sConstructorConstructor %s' % (interface_name, constructor_name) 183 attributes_list.append(extended_string + attribute_string) 184 185 return attributes_list 186 187 188def generate_event_names_file(destination_filename, event_names, only_if_changed): 189 def extended_attribute_string(name): 190 value = extended_attributes[name] 191 if name == 'RuntimeEnabled': 192 value += 'Enabled' 193 return name + '=' + value 194 195 source_dir, _ = os.path.split(os.getcwd()) 196 lines = [] 197 lines.append('namespace="Event"\n') 198 lines.append('\n') 199 for filename, extended_attributes in sorted(event_names.iteritems()): 200 refined_filename, _ = os.path.splitext(os.path.relpath(filename, source_dir)) 201 refined_filename = refined_filename.replace(os.sep, posixpath.sep) 202 extended_attributes_list = [ 203 extended_attribute_string(name) 204 for name in 'Conditional', 'ImplementedAs', 'RuntimeEnabled' 205 if name in extended_attributes] 206 lines.append('%s %s\n' % (refined_filename, ', '.join(extended_attributes_list))) 207 write_file(lines, destination_filename, only_if_changed) 208 209 210def generate_global_constructors_partial_interface(interface_name, destination_filename, constructor_attributes_list, only_if_changed): 211 lines = (['partial interface %s {\n' % interface_name] + 212 [' %s;\n' % constructor_attribute 213 for constructor_attribute in sorted(constructor_attributes_list)] + 214 ['};\n']) 215 write_file(lines, destination_filename, only_if_changed) 216 217 218def generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere): 219 interface_name, _ = os.path.splitext(os.path.basename(idl_file_name)) 220 full_path = os.path.realpath(idl_file_name) 221 idl_file_contents = get_file_contents(full_path) 222 223 # Handle partial interfaces 224 partial_interface_name = get_partial_interface_name_from_idl(idl_file_contents) 225 if partial_interface_name: 226 partial_interface_files[partial_interface_name].append(full_path) 227 return partial_interface_name 228 229 interfaces.add(interface_name) 230 # Non-partial interfaces default to having bindings generated 231 dependencies[full_path] = [] 232 233 # Parse 'identifier-A implements identifier-B;' statements 234 implemented_interfaces = get_implemented_interfaces_from_idl(idl_file_contents, interface_name) 235 implements_interfaces[interface_name] = implemented_interfaces 236 implemented_somewhere |= set(implemented_interfaces) 237 238 return partial_interface_name 239 240 241def remove_interfaces_implemented_somewhere(dependencies, interface_name_to_idl_file, implemented_somewhere): 242 # Interfaces that are implemented by another interface do not have 243 # their own bindings generated, as this would be redundant with the 244 # actual implementation. 245 for implemented_interface in implemented_somewhere: 246 full_path = interface_name_to_idl_file[implemented_interface] 247 del dependencies[full_path] 248 249 250def record_global_constructors_and_extended_attribute(idl_file_name, global_constructors, interface_extended_attribute, parent_interface): 251 interface_name, _ = os.path.splitext(os.path.basename(idl_file_name)) 252 full_path = os.path.realpath(idl_file_name) 253 idl_file_contents = get_file_contents(full_path) 254 extended_attributes = get_interface_extended_attributes_from_idl(idl_file_contents) 255 256 # Record global constructors 257 if not is_callback_interface_from_idl(idl_file_contents) and 'NoInterfaceObject' not in extended_attributes: 258 global_contexts = extended_attributes.get('GlobalContext', 'Window').split('&') 259 new_constructor_list = generate_constructor_attribute_list(interface_name, extended_attributes) 260 for global_object in global_contexts: 261 global_constructors[global_object].extend(new_constructor_list) 262 263 # Record parents and extended attributes for generating event names 264 if interface_name == 'Event': 265 interface_extended_attribute[interface_name] = extended_attributes 266 parent = get_parent_interface(idl_file_contents) 267 if parent: 268 parent_interface[interface_name] = parent 269 interface_extended_attribute[interface_name] = extended_attributes 270 271 272def parse_idl_files(main_idl_files, support_idl_files, global_constructors_filenames): 273 """Return dependencies between IDL files, constructors on global objects, and events. 274 275 Returns: 276 interfaces: 277 set of all interfaces 278 bindings_derived_sources: 279 list of main IDL file names (except support IDL file names) 280 dependencies: 281 dict of main IDL filename (for a given interface) -> list of partial IDL filenames (for that interface) 282 The keys (main IDL files) are the files for which bindings are 283 generated. This does not include IDL files for interfaces 284 implemented by another interface. 285 global_constructors: 286 dict of global objects -> list of constructors on that object 287 event_names: 288 dict of interfaces that inherit from Event -> list of extended attributes for the interface 289 """ 290 interfaces = set() 291 dependencies = {} 292 partial_interface_files = {} 293 implements_interfaces = {} 294 implemented_somewhere = set() 295 296 global_constructors = {} 297 for global_object in global_constructors_filenames.keys(): 298 global_constructors[global_object] = [] 299 300 # Parents and extended attributes (of interfaces with parents) are 301 # used in generating event names 302 parent_interface = {} 303 interface_extended_attribute = {} 304 305 interface_name_to_idl_file = {} 306 for idl_file_name in main_idl_files + support_idl_files: 307 full_path = os.path.realpath(idl_file_name) 308 interface_name, _ = os.path.splitext(os.path.basename(idl_file_name)) 309 interface_name_to_idl_file[interface_name] = full_path 310 partial_interface_files[interface_name] = [] 311 312 # Generate dependencies, global_constructors and interface_extended_attributes for main IDL files 313 for idl_file_name in main_idl_files: 314 if not generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere): 315 record_global_constructors_and_extended_attribute(idl_file_name, global_constructors, interface_extended_attribute, parent_interface) 316 317 bindings_derived_sources = dependencies.copy() 318 remove_interfaces_implemented_somewhere(bindings_derived_sources, interface_name_to_idl_file, implemented_somewhere) 319 320 # Add constructors on global objects to partial interfaces 321 for global_object, filename in global_constructors_filenames.iteritems(): 322 if global_object in interfaces: 323 partial_interface_files[global_object].append(filename) 324 325 # Add support IDL files to the dependencies for supporting partial interface 326 for idl_file_name in support_idl_files: 327 generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere) 328 remove_interfaces_implemented_somewhere(dependencies, interface_name_to_idl_file, implemented_somewhere) 329 330 # An IDL file's dependencies are partial interface files that extend it, 331 # and files for other interfaces that this interfaces implements. 332 for idl_file_path in dependencies.iterkeys(): 333 interface_name, _ = os.path.splitext(os.path.basename(idl_file_path)) 334 implemented_interfaces = implements_interfaces[interface_name] 335 try: 336 interface_paths = map(lambda x: interface_name_to_idl_file[x], implemented_interfaces) 337 except KeyError as key_name: 338 raise IdlInterfaceFileNotFoundError('Could not find the IDL file where the following implemented interface is defined: %s' % key_name) 339 dependencies[idl_file_path] = sorted(partial_interface_files[interface_name] + interface_paths) 340 341 # Generate event names for all interfaces that inherit from Event, 342 # including Event itself. 343 event_names = {} 344 if 'Event' in interfaces: 345 event_names[interface_name_to_idl_file['Event']] = interface_extended_attribute['Event'] 346 for interface, parent in parent_interface.iteritems(): 347 while parent in parent_interface: 348 parent = parent_interface[parent] 349 if parent == 'Event': 350 event_names[interface_name_to_idl_file[interface]] = interface_extended_attribute[interface] 351 352 return interfaces, dependencies, bindings_derived_sources, global_constructors, event_names 353 354 355def write_dependency_file(filename, dependencies, only_if_changed): 356 """Write the interface dependencies file. 357 358 The format is as follows: 359 360 Document.idl P.idl 361 Event.idl 362 Window.idl Q.idl R.idl S.idl 363 ... 364 365 The above indicates that: 366 Document.idl depends on P.idl, 367 Event.idl depends on no other IDL files, and 368 Window.idl depends on Q.idl, R.idl, and S.idl. 369 370 An IDL that is a dependency of another IDL (e.g. P.idl) does not have its 371 own line in the dependency file. 372 """ 373 lines = ['%s %s\n' % (idl_file, ' '.join(sorted(dependency_files))) 374 for idl_file, dependency_files in sorted(dependencies.iteritems())] 375 write_file(lines, filename, only_if_changed) 376 377 378def main(): 379 options = parse_options() 380 with open(options.main_idl_files_list) as idl_files_list: 381 main_idl_files = [string.rstrip(line, '\n') for line in idl_files_list] 382 with open(options.support_idl_files_list) as idl_files_list: 383 support_idl_files = [string.rstrip(line, '\n') for line in idl_files_list] 384 only_if_changed = options.write_file_only_if_changed 385 global_constructors_filenames = { 386 'Window': options.window_constructors_file, 387 'WorkerGlobalScope': options.workerglobalscope_constructors_file, 388 'SharedWorkerGlobalScope': options.sharedworkerglobalscope_constructors_file, 389 'DedicatedWorkerGlobalScope': options.dedicatedworkerglobalscope_constructors_file, 390 'ServiceWorkerGlobalScope': options.serviceworkerglobalscope_constructors_file, 391 } 392 393 interfaces, dependencies, bindings_derived_sources, global_constructors, event_names = parse_idl_files(main_idl_files, support_idl_files, global_constructors_filenames) 394 395 write_dependency_file(options.interface_dependencies_file, dependencies, only_if_changed) 396 write_dependency_file(options.bindings_derived_sources_file, bindings_derived_sources, only_if_changed) 397 for interface_name, filename in global_constructors_filenames.iteritems(): 398 if interface_name in interfaces: 399 generate_global_constructors_partial_interface(interface_name, filename, global_constructors[interface_name], only_if_changed) 400 generate_event_names_file(options.event_names_file, event_names, only_if_changed) 401 402 403if __name__ == '__main__': 404 main() 405