| OLD | NEW |
| (Empty) |
| 1 # Copyright (C) 2013 Google Inc. All rights reserved. | |
| 2 # | |
| 3 # Redistribution and use in source and binary forms, with or without | |
| 4 # modification, are permitted provided that the following conditions are | |
| 5 # met: | |
| 6 # | |
| 7 # * Redistributions of source code must retain the above copyright | |
| 8 # notice, this list of conditions and the following disclaimer. | |
| 9 # * Redistributions in binary form must reproduce the above | |
| 10 # copyright notice, this list of conditions and the following disclaimer | |
| 11 # in the documentation and/or other materials provided with the | |
| 12 # distribution. | |
| 13 # * Neither the name of Google Inc. nor the names of its | |
| 14 # contributors may be used to endorse or promote products derived from | |
| 15 # this software without specific prior written permission. | |
| 16 # | |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 28 | |
| 29 import optparse | |
| 30 import os | |
| 31 import os.path | |
| 32 import re | |
| 33 import string | |
| 34 | |
| 35 | |
| 36 def parse_options(): | |
| 37 parser = optparse.OptionParser() | |
| 38 parser.add_option('--event-names-file', help='output file') | |
| 39 parser.add_option('--idl-files-list', help='file listing all IDLs') | |
| 40 parser.add_option('--supplemental-dependency-file', help='output file') | |
| 41 parser.add_option('--window-constructors-file', help='output file') | |
| 42 parser.add_option('--workerglobalscope-constructors-file', help='output file
') | |
| 43 parser.add_option('--sharedworkerglobalscope-constructors-file', help='outpu
t file') | |
| 44 parser.add_option('--dedicatedworkerglobalscope-constructors-file', help='ou
tput file') | |
| 45 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') | |
| 46 options, args = parser.parse_args() | |
| 47 if options.event_names_file is None: | |
| 48 parser.error('Must specify an output file using --event-names-file.') | |
| 49 if options.supplemental_dependency_file is None: | |
| 50 parser.error('Must specify an output file using --supplemental-dependenc
y-file.') | |
| 51 if options.window_constructors_file is None: | |
| 52 parser.error('Must specify an output file using --window-constructors-fi
le.') | |
| 53 if options.workerglobalscope_constructors_file is None: | |
| 54 parser.error('Must specify an output file using --workerglobalscope-cons
tructors-file.') | |
| 55 if options.workerglobalscope_constructors_file is None: | |
| 56 parser.error('Must specify an output file using --sharedworkerglobalscop
e-constructors-file.') | |
| 57 if options.workerglobalscope_constructors_file is None: | |
| 58 parser.error('Must specify an output file using --dedicatedworkerglobals
cope-constructors-file.') | |
| 59 if options.idl_files_list is None: | |
| 60 parser.error('Must specify the file listing all IDLs using --idl-files-l
ist.') | |
| 61 if options.write_file_only_if_changed is None: | |
| 62 parser.error('Must specify whether file is only written if changed using
--write-file-only-if-changed.') | |
| 63 options.write_file_only_if_changed = bool(options.write_file_only_if_changed
) | |
| 64 if args: | |
| 65 parser.error('No arguments taken, but "%s" given.' % ' '.join(args)) | |
| 66 return options | |
| 67 | |
| 68 | |
| 69 def get_file_contents(idl_filename): | |
| 70 with open(idl_filename) as idl_file: | |
| 71 lines = idl_file.readlines() | |
| 72 return ''.join(lines) | |
| 73 | |
| 74 | |
| 75 def write_file(new_lines, destination_filename, only_if_changed): | |
| 76 if only_if_changed and os.path.isfile(destination_filename): | |
| 77 with open(destination_filename) as destination_file: | |
| 78 old_lines = destination_file.readlines() | |
| 79 if old_lines == new_lines: | |
| 80 return | |
| 81 with open(destination_filename, 'w') as destination_file: | |
| 82 destination_file.write(''.join(new_lines)) | |
| 83 | |
| 84 | |
| 85 def get_partial_interface_name_from_idl(file_contents): | |
| 86 match = re.search(r'partial\s+interface\s+(\w+)', file_contents) | |
| 87 if match: | |
| 88 return match.group(1) | |
| 89 return None | |
| 90 | |
| 91 | |
| 92 # identifier-A implements identifier-B; | |
| 93 # http://www.w3.org/TR/WebIDL/#idl-implements-statements | |
| 94 def get_implemented_interfaces_from_idl(file_contents, interface_name): | |
| 95 implemented_interfaces = [] | |
| 96 for match in re.finditer(r'^\s*(\w+)\s+implements\s+(\w+)\s*;', file_content
s, re.MULTILINE): | |
| 97 # identifier-A must be the current interface | |
| 98 assert match.group(1) == interface_name, \ | |
| 99 "Identifier on the left of the 'implements' statement should be %s in %s.idl, bu
t found %s" % (interface_name, interface_name, match.group(1)) | |
| 100 implemented_interfaces.append(match.group(2)) | |
| 101 return implemented_interfaces | |
| 102 | |
| 103 def is_callback_interface_from_idl(file_contents): | |
| 104 match = re.search(r'callback\s+interface\s+\w+', file_contents) | |
| 105 return match is not None | |
| 106 | |
| 107 | |
| 108 def get_parent_interface(file_contents): | |
| 109 match = re.search(r'interface\s+\w+\s*:\s*(\w+)\s*', file_contents) | |
| 110 if match: | |
| 111 return match.group(1) | |
| 112 return None | |
| 113 | |
| 114 def get_interface_extended_attributes_from_idl(file_contents): | |
| 115 extended_attributes = {} | |
| 116 match = re.search(r'\[(.*)\]\s+(interface|exception)\s+(\w+)', | |
| 117 file_contents, flags=re.DOTALL) | |
| 118 if match: | |
| 119 parts = string.split(match.group(1), ',') | |
| 120 for part in parts: | |
| 121 # Match 'key = value' | |
| 122 match = re.match(r'([^=\s]*)(?:\s*=\s*(.*))?', part.strip()) | |
| 123 key = match.group(1) | |
| 124 value = match.group(2) or 'VALUE_IS_MISSING' | |
| 125 if not key: | |
| 126 continue | |
| 127 extended_attributes[key] = value | |
| 128 return extended_attributes | |
| 129 | |
| 130 | |
| 131 def generate_constructor_attribute_list(interface_name, extended_attributes): | |
| 132 extended_attributes_list = [] | |
| 133 for attribute_name, attribute_value in extended_attributes.iteritems(): | |
| 134 if attribute_name not in ['Conditional', 'EnabledAtRuntime', 'EnabledPer
Context']: | |
| 135 continue | |
| 136 extended_attribute = attribute_name | |
| 137 if attribute_value != 'VALUE_IS_MISSING': | |
| 138 extended_attribute += '=' + attribute_value | |
| 139 extended_attributes_list.append(extended_attribute) | |
| 140 if extended_attributes_list: | |
| 141 extended_string = '[%s] ' % ', '.join(extended_attributes_list) | |
| 142 else: | |
| 143 extended_string = '' | |
| 144 | |
| 145 attribute_string = 'attribute %(interface_name)sConstructor %(interface_name
)s' % {'interface_name': interface_name} | |
| 146 attributes_list = [extended_string + attribute_string] | |
| 147 | |
| 148 # In addition to the regular property, for every [NamedConstructor] | |
| 149 # extended attribute on an interface, a corresponding property MUST exist | |
| 150 # on the ECMAScript global object. | |
| 151 if 'NamedConstructor' in extended_attributes: | |
| 152 named_constructor = extended_attributes['NamedConstructor'] | |
| 153 # Extract function name, namely everything before opening '(' | |
| 154 constructor_name = re.sub(r'\(.*', '', named_constructor) | |
| 155 # Note the reduplicated 'ConstructorConstructor' | |
| 156 attribute_string = 'attribute %sConstructorConstructor %s' % (interface_
name, constructor_name) | |
| 157 attributes_list.append(extended_string + attribute_string) | |
| 158 | |
| 159 return attributes_list | |
| 160 | |
| 161 | |
| 162 def generate_event_names_file(destination_filename, event_names, only_if_changed
=False): | |
| 163 source_dir, _ = os.path.split(os.getcwd()) | |
| 164 lines = [] | |
| 165 lines.append('namespace="Event"\n') | |
| 166 lines.append('\n') | |
| 167 for filename in event_names: | |
| 168 attributes = [] | |
| 169 extended_attributes = event_names[filename] | |
| 170 for key in ('ImplementedAs', 'Conditional', 'EnabledAtRuntime'): | |
| 171 suffix = '' | |
| 172 if key == 'EnabledAtRuntime': | |
| 173 suffix = 'Enabled' | |
| 174 if key in extended_attributes: | |
| 175 attributes.append('%s=%s%s' % (key, extended_attributes[key], su
ffix)) | |
| 176 refined_filename, _ = os.path.splitext(os.path.relpath(filename, source_
dir)) | |
| 177 lines.append('%s %s\n' % (refined_filename, ', '.join(attributes))) | |
| 178 write_file(lines, destination_filename, only_if_changed) | |
| 179 | |
| 180 | |
| 181 def generate_global_constructors_partial_interface(interface_name, destination_f
ilename, constructor_attributes_list, only_if_changed=False): | |
| 182 lines = [] | |
| 183 lines.append('partial interface %s {\n' % interface_name) | |
| 184 for constructor_attribute in constructor_attributes_list: | |
| 185 lines.append(' %s;\n' % constructor_attribute) | |
| 186 lines.append('};\n') | |
| 187 write_file(lines, destination_filename, only_if_changed) | |
| 188 | |
| 189 | |
| 190 def parse_idl_files(idl_files, window_constructors_filename, workerglobalscope_c
onstructors_filename, sharedworkerglobalscope_constructors_filename, dedicatedwo
rkerglobalscope_constructors_filename, event_names_file, only_if_changed=False): | |
| 191 interface_name_to_idl_file = {} | |
| 192 idl_file_to_interface_name = {} | |
| 193 supplemental_dependencies = {} | |
| 194 supplementals = {} | |
| 195 event_names = {} | |
| 196 window_constructor_attributes_list = [] | |
| 197 workerglobalscope_constructor_attributes_list = [] | |
| 198 sharedworkerglobalscope_constructor_attributes_list = [] | |
| 199 dedicatedworkerglobalscope_constructor_attributes_list = [] | |
| 200 | |
| 201 parent_interface = {} | |
| 202 interface_extended_attribute = {} | |
| 203 interface_to_file = {} | |
| 204 | |
| 205 # Populate interface_name_to_idl_file first | |
| 206 for idl_file_name in idl_files: | |
| 207 full_path = os.path.realpath(idl_file_name) | |
| 208 interface_name, _ = os.path.splitext(os.path.basename(idl_file_name)) | |
| 209 interface_name_to_idl_file[interface_name] = full_path | |
| 210 | |
| 211 for idl_file_name in idl_files: | |
| 212 full_path = os.path.realpath(idl_file_name) | |
| 213 interface_name, _ = os.path.splitext(os.path.basename(idl_file_name)) | |
| 214 idl_file_contents = get_file_contents(full_path) | |
| 215 | |
| 216 if interface_name == 'Event': | |
| 217 event_names[idl_file_name] = get_interface_extended_attributes_from_
idl(idl_file_contents) | |
| 218 else: | |
| 219 parent = get_parent_interface(idl_file_contents) | |
| 220 if parent: | |
| 221 parent_interface[interface_name] = parent | |
| 222 interface_extended_attribute[interface_name] = get_interface_ext
ended_attributes_from_idl(idl_file_contents) | |
| 223 interface_to_file[interface_name] = idl_file_name | |
| 224 | |
| 225 # Handle partial interfaces | |
| 226 partial_interface_name = get_partial_interface_name_from_idl(idl_file_co
ntents) | |
| 227 if partial_interface_name: | |
| 228 supplemental_dependencies[full_path] = [partial_interface_name] | |
| 229 continue | |
| 230 | |
| 231 # Parse 'identifier-A implements identifier-B; statements | |
| 232 implemented_interfaces = get_implemented_interfaces_from_idl(idl_file_co
ntents, interface_name) | |
| 233 for implemented_interface in implemented_interfaces: | |
| 234 assert implemented_interface in interface_name_to_idl_file, \ | |
| 235 "Could not find a the IDL file where the following implemented interface is defi
ned: %s" % implemented_interface | |
| 236 supplemental_dependencies.setdefault(interface_name_to_idl_file[impl
emented_interface], []).append(interface_name) | |
| 237 # Handle [NoInterfaceObject] | |
| 238 if not is_callback_interface_from_idl(idl_file_contents): | |
| 239 extended_attributes = get_interface_extended_attributes_from_idl(idl
_file_contents) | |
| 240 if 'NoInterfaceObject' not in extended_attributes: | |
| 241 global_contexts = extended_attributes.get('GlobalContext', 'Wind
ow').split('&') | |
| 242 constructor_list = generate_constructor_attribute_list(interface
_name, extended_attributes) | |
| 243 if 'Window' in global_contexts: | |
| 244 window_constructor_attributes_list.extend(constructor_list) | |
| 245 if 'WorkerGlobalScope' in global_contexts: | |
| 246 workerglobalscope_constructor_attributes_list.extend(constru
ctor_list) | |
| 247 if 'SharedWorkerGlobalScope' in global_contexts: | |
| 248 sharedworkerglobalscope_constructor_attributes_list.extend(c
onstructor_list) | |
| 249 if 'DedicatedWorkerGlobalScope' in global_contexts: | |
| 250 dedicatedworkerglobalscope_constructor_attributes_list.exten
d(constructor_list) | |
| 251 idl_file_to_interface_name[full_path] = interface_name | |
| 252 supplementals[full_path] = [] | |
| 253 | |
| 254 for interface in parent_interface: | |
| 255 parent = parent_interface[interface] | |
| 256 while parent in parent_interface: | |
| 257 parent = parent_interface[parent] | |
| 258 if parent == 'Event': | |
| 259 event_names[interface_to_file[interface]] = interface_extended_attri
bute[interface] | |
| 260 generate_event_names_file(event_names_file, event_names, only_if_changed=onl
y_if_changed) | |
| 261 | |
| 262 # Generate Global constructors | |
| 263 if 'Window' in interface_name_to_idl_file: | |
| 264 generate_global_constructors_partial_interface("Window", window_construc
tors_filename, window_constructor_attributes_list, only_if_changed=only_if_chang
ed) | |
| 265 supplemental_dependencies[window_constructors_filename] = ['Window'] | |
| 266 if 'WorkerGlobalScope' in interface_name_to_idl_file: | |
| 267 generate_global_constructors_partial_interface("WorkerGlobalScope", work
erglobalscope_constructors_filename, workerglobalscope_constructor_attributes_li
st, only_if_changed=only_if_changed) | |
| 268 supplemental_dependencies[workerglobalscope_constructors_filename] = ['W
orkerGlobalScope'] | |
| 269 if 'SharedWorkerGlobalScope' in interface_name_to_idl_file: | |
| 270 generate_global_constructors_partial_interface("SharedWorkerGlobalScope"
, sharedworkerglobalscope_constructors_filename, sharedworkerglobalscope_constru
ctor_attributes_list, only_if_changed=only_if_changed) | |
| 271 supplemental_dependencies[sharedworkerglobalscope_constructors_filename]
= ['SharedWorkerGlobalScope'] | |
| 272 if 'DedicatedWorkerGlobalScope' in interface_name_to_idl_file: | |
| 273 generate_global_constructors_partial_interface("DedicatedWorkerGlobalSco
pe", dedicatedworkerglobalscope_constructors_filename, dedicatedworkerglobalscop
e_constructor_attributes_list, only_if_changed=only_if_changed) | |
| 274 supplemental_dependencies[dedicatedworkerglobalscope_constructors_filena
me] = ['DedicatedWorkerGlobalScope'] | |
| 275 | |
| 276 # Resolve partial interfaces dependencies | |
| 277 for idl_file, base_files in supplemental_dependencies.iteritems(): | |
| 278 for base_file in base_files: | |
| 279 target_idl_file = interface_name_to_idl_file[base_file] | |
| 280 supplementals[target_idl_file].append(idl_file) | |
| 281 if idl_file in supplementals: | |
| 282 # Should never occur. Might be needed in corner cases. | |
| 283 del supplementals[idl_file] | |
| 284 return supplementals | |
| 285 | |
| 286 | |
| 287 def write_dependency_file(filename, supplementals, only_if_changed=False): | |
| 288 """Outputs the dependency file. | |
| 289 | |
| 290 The format of a supplemental dependency file: | |
| 291 | |
| 292 Window.idl P.idl Q.idl R.idl | |
| 293 Document.idl S.idl | |
| 294 Event.idl | |
| 295 ... | |
| 296 | |
| 297 The above indicates that: | |
| 298 Window.idl is supplemented by P.idl, Q.idl and R.idl, | |
| 299 Document.idl is supplemented by S.idl, and | |
| 300 Event.idl is supplemented by no IDLs. | |
| 301 | |
| 302 An IDL that supplements another IDL (e.g. P.idl) does not have its own | |
| 303 lines in the dependency file. | |
| 304 """ | |
| 305 lines = [] | |
| 306 for idl_file, supplemental_files in sorted(supplementals.iteritems()): | |
| 307 lines.append('%s %s\n' % (idl_file, ' '.join(supplemental_files))) | |
| 308 write_file(lines, filename, only_if_changed) | |
| 309 | |
| 310 | |
| 311 def main(): | |
| 312 options = parse_options() | |
| 313 idl_files = [] | |
| 314 with open(options.idl_files_list) as idl_files_list_file: | |
| 315 for line in idl_files_list_file: | |
| 316 idl_files.append(string.rstrip(line, '\n')) | |
| 317 resolved_supplementals = parse_idl_files(idl_files, options.window_construct
ors_file, options.workerglobalscope_constructors_file, options.sharedworkergloba
lscope_constructors_file, options.dedicatedworkerglobalscope_constructors_file,
options.event_names_file, only_if_changed=options.write_file_only_if_changed) | |
| 318 write_dependency_file(options.supplemental_dependency_file, resolved_supplem
entals, only_if_changed=options.write_file_only_if_changed) | |
| 319 | |
| 320 | |
| 321 if __name__ == '__main__': | |
| 322 main() | |
| OLD | NEW |