• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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