summaryrefslogtreecommitdiff
path: root/chromium/third_party/WebKit/Source/bindings/scripts/compute_dependencies.py
blob: b358ae60e01aa6fbe16d1a07e423ba7693645e6e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#!/usr/bin/python
#
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import optparse
import os
import posixpath
import re
import string


class IdlBadFilenameError(Exception):
    """Raised if an IDL filename disagrees with the interface name in the file."""
    pass


class IdlInterfaceFileNotFoundError(Exception):
    """Raised if the IDL file implementing an interface cannot be found."""
    pass


def parse_options():
    parser = optparse.OptionParser()
    parser.add_option('--event-names-file', help='output file')
    parser.add_option('--main-idl-files-list', help='file listing main (compiled to Blink) IDL files')
    parser.add_option('--support-idl-files-list', help='file listing support IDL files (not compiled to Blink, e.g. testing)')
    parser.add_option('--interface-dependencies-file', help='output file')
    parser.add_option('--bindings-derived-sources-file', help='output file')
    parser.add_option('--window-constructors-file', help='output file')
    parser.add_option('--workerglobalscope-constructors-file', help='output file')
    parser.add_option('--sharedworkerglobalscope-constructors-file', help='output file')
    parser.add_option('--dedicatedworkerglobalscope-constructors-file', help='output file')
    parser.add_option('--serviceworkerglobalscope-constructors-file', help='output file')
    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')
    options, args = parser.parse_args()
    if options.event_names_file is None:
        parser.error('Must specify an output file using --event-names-file.')
    if options.interface_dependencies_file is None:
        parser.error('Must specify an output file using --interface-dependencies-file.')
    if options.bindings_derived_sources_file is None:
        parser.error('Must specify an output file using --bindings-derived-sources-file.')
    if options.window_constructors_file is None:
        parser.error('Must specify an output file using --window-constructors-file.')
    if options.workerglobalscope_constructors_file is None:
        parser.error('Must specify an output file using --workerglobalscope-constructors-file.')
    if options.sharedworkerglobalscope_constructors_file is None:
        parser.error('Must specify an output file using --sharedworkerglobalscope-constructors-file.')
    if options.dedicatedworkerglobalscope_constructors_file is None:
        parser.error('Must specify an output file using --dedicatedworkerglobalscope-constructors-file.')
    if options.serviceworkerglobalscope_constructors_file is None:
        parser.error('Must specify an output file using --serviceworkerglobalscope-constructors-file.')
    if options.main_idl_files_list is None:
        parser.error('Must specify a file listing main IDL files using --main-idl-files-list.')
    if options.support_idl_files_list is None:
        parser.error('Must specify a file listing support IDL files using --support-idl-files-list.')
    if options.write_file_only_if_changed is None:
        parser.error('Must specify whether file is only written if changed using --write-file-only-if-changed.')
    options.write_file_only_if_changed = bool(options.write_file_only_if_changed)
    if args:
        parser.error('No arguments taken, but "%s" given.' % ' '.join(args))
    return options


def get_file_contents(idl_filename):
    with open(idl_filename) as idl_file:
        lines = idl_file.readlines()
    return ''.join(lines)


def write_file(new_lines, destination_filename, only_if_changed):
    if only_if_changed and os.path.isfile(destination_filename):
        with open(destination_filename) as destination_file:
            old_lines = destination_file.readlines()
        if old_lines == new_lines:
            return
    with open(destination_filename, 'w') as destination_file:
        destination_file.write(''.join(new_lines))


def get_partial_interface_name_from_idl(file_contents):
    match = re.search(r'partial\s+interface\s+(\w+)', file_contents)
    return match and match.group(1)


# identifier-A implements identifier-B;
# http://www.w3.org/TR/WebIDL/#idl-implements-statements
def get_implemented_interfaces_from_idl(file_contents, interface_name):
    def get_implemented(left_identifier, right_identifier):
        # identifier-A must be the current interface
        if left_identifier != interface_name:
            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))
        return right_identifier

    implements_re = r'^\s*(\w+)\s+implements\s+(\w+)\s*;'
    implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE)
    implements_pairs = [(match.group(1), match.group(2))
                        for match in implements_matches]
    return [get_implemented(left, right) for left, right in implements_pairs]


def is_callback_interface_from_idl(file_contents):
    match = re.search(r'callback\s+interface\s+\w+', file_contents)
    return bool(match)


def get_parent_interface(file_contents):
    match = re.search(r'interface\s+\w+\s*:\s*(\w+)\s*', file_contents)
    return match and match.group(1)


def get_interface_extended_attributes_from_idl(file_contents):
    match = re.search(r'\[(.*)\]\s+(callback\s+)?(interface|exception)\s+(\w+)',
                      file_contents, flags=re.DOTALL)
    if not match:
        return {}
    # Strip comments
    # re.compile needed b/c Python 2.6 doesn't support flags in re.sub
    single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE)
    block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL)
    extended_attributes_string = re.sub(single_line_comment_re, '', match.group(1))
    extended_attributes_string = re.sub(block_comment_re, '', extended_attributes_string)
    extended_attributes = {}
    # FIXME: this splitting is WRONG: it fails on ExtendedAttributeArgList like
    # 'NamedConstructor=Foo(a, b)'
    parts = [extended_attribute.strip()
             for extended_attribute in extended_attributes_string.split(',')
             # Discard empty parts, which may exist due to trailing comma
             if extended_attribute.strip()]
    for part in parts:
        name, _, value = map(string.strip, part.partition('='))
        extended_attributes[name] = value
    return extended_attributes


def generate_constructor_attribute_list(interface_name, extended_attributes):
    extended_attributes_list = [
            name + '=' + extended_attributes[name]
            for name in 'Conditional', 'PerContextEnabled', 'RuntimeEnabled'
            if name in extended_attributes]
    if extended_attributes_list:
        extended_string = '[%s] ' % ', '.join(extended_attributes_list)
    else:
        extended_string = ''

    attribute_string = 'attribute {interface_name}Constructor {interface_name}'.format(interface_name=interface_name)
    attributes_list = [extended_string + attribute_string]

    # In addition to the regular property, for every [NamedConstructor]
    # extended attribute on an interface, a corresponding property MUST exist
    # on the ECMAScript global object.
    if 'NamedConstructor' in extended_attributes:
        named_constructor = extended_attributes['NamedConstructor']
        # Extract function name, namely everything before opening '('
        constructor_name = re.sub(r'\(.*', '', named_constructor)
        # Note the reduplicated 'ConstructorConstructor'
        attribute_string = 'attribute %sConstructorConstructor %s' % (interface_name, constructor_name)
        attributes_list.append(extended_string + attribute_string)

    return attributes_list


def generate_event_names_file(destination_filename, event_names, only_if_changed):
    def extended_attribute_string(name):
        value = extended_attributes[name]
        if name == 'RuntimeEnabled':
            value += 'Enabled'
        return name + '=' + value

    source_dir, _ = os.path.split(os.getcwd())
    lines = []
    lines.append('namespace="Event"\n')
    lines.append('\n')
    for filename, extended_attributes in sorted(event_names.iteritems()):
        refined_filename, _ = os.path.splitext(os.path.relpath(filename, source_dir))
        refined_filename = refined_filename.replace(os.sep, posixpath.sep)
        extended_attributes_list = [
                extended_attribute_string(name)
                for name in 'Conditional', 'ImplementedAs', 'RuntimeEnabled'
                if name in extended_attributes]
        lines.append('%s %s\n' % (refined_filename, ', '.join(extended_attributes_list)))
    write_file(lines, destination_filename, only_if_changed)


def generate_global_constructors_partial_interface(interface_name, destination_filename, constructor_attributes_list, only_if_changed):
    lines = (['partial interface %s {\n' % interface_name] +
             ['    %s;\n' % constructor_attribute
              for constructor_attribute in sorted(constructor_attributes_list)] +
             ['};\n'])
    write_file(lines, destination_filename, only_if_changed)


def generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere):
    interface_name, _ = os.path.splitext(os.path.basename(idl_file_name))
    full_path = os.path.realpath(idl_file_name)
    idl_file_contents = get_file_contents(full_path)

    # Handle partial interfaces
    partial_interface_name = get_partial_interface_name_from_idl(idl_file_contents)
    if partial_interface_name:
        partial_interface_files[partial_interface_name].append(full_path)
        return partial_interface_name

    interfaces.add(interface_name)
    # Non-partial interfaces default to having bindings generated
    dependencies[full_path] = []

    # Parse 'identifier-A implements identifier-B;' statements
    implemented_interfaces = get_implemented_interfaces_from_idl(idl_file_contents, interface_name)
    implements_interfaces[interface_name] = implemented_interfaces
    implemented_somewhere |= set(implemented_interfaces)

    return partial_interface_name


def remove_interfaces_implemented_somewhere(dependencies, interface_name_to_idl_file, implemented_somewhere):
    # Interfaces that are implemented by another interface do not have
    # their own bindings generated, as this would be redundant with the
    # actual implementation.
    for implemented_interface in implemented_somewhere:
        full_path = interface_name_to_idl_file[implemented_interface]
        del dependencies[full_path]


def record_global_constructors_and_extended_attribute(idl_file_name, global_constructors, interface_extended_attribute, parent_interface):
    interface_name, _ = os.path.splitext(os.path.basename(idl_file_name))
    full_path = os.path.realpath(idl_file_name)
    idl_file_contents = get_file_contents(full_path)
    extended_attributes = get_interface_extended_attributes_from_idl(idl_file_contents)

    # Record global constructors
    if not is_callback_interface_from_idl(idl_file_contents) and 'NoInterfaceObject' not in extended_attributes:
        global_contexts = extended_attributes.get('GlobalContext', 'Window').split('&')
        new_constructor_list = generate_constructor_attribute_list(interface_name, extended_attributes)
        for global_object in global_contexts:
            global_constructors[global_object].extend(new_constructor_list)

    # Record parents and extended attributes for generating event names
    if interface_name == 'Event':
        interface_extended_attribute[interface_name] = extended_attributes
    parent = get_parent_interface(idl_file_contents)
    if parent:
        parent_interface[interface_name] = parent
        interface_extended_attribute[interface_name] = extended_attributes


def parse_idl_files(main_idl_files, support_idl_files, global_constructors_filenames):
    """Return dependencies between IDL files, constructors on global objects, and events.

    Returns:
        interfaces:
            set of all interfaces
        bindings_derived_sources:
            list of main IDL file names (except support IDL file names)
        dependencies:
            dict of main IDL filename (for a given interface) -> list of partial IDL filenames (for that interface)
            The keys (main IDL files) are the files for which bindings are
            generated. This does not include IDL files for interfaces
            implemented by another interface.
        global_constructors:
            dict of global objects -> list of constructors on that object
        event_names:
            dict of interfaces that inherit from Event -> list of extended attributes for the interface
    """
    interfaces = set()
    dependencies = {}
    partial_interface_files = {}
    implements_interfaces = {}
    implemented_somewhere = set()

    global_constructors = {}
    for global_object in global_constructors_filenames.keys():
        global_constructors[global_object] = []

    # Parents and extended attributes (of interfaces with parents) are
    # used in generating event names
    parent_interface = {}
    interface_extended_attribute = {}

    interface_name_to_idl_file = {}
    for idl_file_name in main_idl_files + support_idl_files:
        full_path = os.path.realpath(idl_file_name)
        interface_name, _ = os.path.splitext(os.path.basename(idl_file_name))
        interface_name_to_idl_file[interface_name] = full_path
        partial_interface_files[interface_name] = []

    # Generate dependencies, global_constructors and interface_extended_attributes for main IDL files
    for idl_file_name in main_idl_files:
        if not generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere):
            record_global_constructors_and_extended_attribute(idl_file_name, global_constructors, interface_extended_attribute, parent_interface)

    bindings_derived_sources = dependencies.copy()
    remove_interfaces_implemented_somewhere(bindings_derived_sources, interface_name_to_idl_file, implemented_somewhere)

    # Add constructors on global objects to partial interfaces
    for global_object, filename in global_constructors_filenames.iteritems():
        if global_object in interfaces:
            partial_interface_files[global_object].append(filename)

    # Add support IDL files to the dependencies for supporting partial interface
    for idl_file_name in support_idl_files:
        generate_dependencies(idl_file_name, interfaces, dependencies, partial_interface_files, implements_interfaces, implemented_somewhere)
    remove_interfaces_implemented_somewhere(dependencies, interface_name_to_idl_file, implemented_somewhere)

    # An IDL file's dependencies are partial interface files that extend it,
    # and files for other interfaces that this interfaces implements.
    for idl_file_path in dependencies.iterkeys():
        interface_name, _ = os.path.splitext(os.path.basename(idl_file_path))
        implemented_interfaces = implements_interfaces[interface_name]
        try:
            interface_paths = map(lambda x: interface_name_to_idl_file[x], implemented_interfaces)
        except KeyError as key_name:
            raise IdlInterfaceFileNotFoundError('Could not find the IDL file where the following implemented interface is defined: %s' % key_name)
        dependencies[idl_file_path] = sorted(partial_interface_files[interface_name] + interface_paths)

    # Generate event names for all interfaces that inherit from Event,
    # including Event itself.
    event_names = {}
    if 'Event' in interfaces:
        event_names[interface_name_to_idl_file['Event']] = interface_extended_attribute['Event']
    for interface, parent in parent_interface.iteritems():
        while parent in parent_interface:
            parent = parent_interface[parent]
        if parent == 'Event':
            event_names[interface_name_to_idl_file[interface]] = interface_extended_attribute[interface]

    return interfaces, dependencies, bindings_derived_sources, global_constructors, event_names


def write_dependency_file(filename, dependencies, only_if_changed):
    """Write the interface dependencies file.

    The format is as follows:

    Document.idl P.idl
    Event.idl
    Window.idl Q.idl R.idl S.idl
    ...

    The above indicates that:
    Document.idl depends on P.idl,
    Event.idl depends on no other IDL files, and
    Window.idl depends on Q.idl, R.idl, and S.idl.

    An IDL that is a dependency of another IDL (e.g. P.idl) does not have its
    own line in the dependency file.
    """
    lines = ['%s %s\n' % (idl_file, ' '.join(sorted(dependency_files)))
             for idl_file, dependency_files in sorted(dependencies.iteritems())]
    write_file(lines, filename, only_if_changed)


def main():
    options = parse_options()
    with open(options.main_idl_files_list) as idl_files_list:
        main_idl_files = [string.rstrip(line, '\n') for line in idl_files_list]
    with open(options.support_idl_files_list) as idl_files_list:
        support_idl_files = [string.rstrip(line, '\n') for line in idl_files_list]
    only_if_changed = options.write_file_only_if_changed
    global_constructors_filenames = {
        'Window': options.window_constructors_file,
        'WorkerGlobalScope': options.workerglobalscope_constructors_file,
        'SharedWorkerGlobalScope': options.sharedworkerglobalscope_constructors_file,
        'DedicatedWorkerGlobalScope': options.dedicatedworkerglobalscope_constructors_file,
        'ServiceWorkerGlobalScope': options.serviceworkerglobalscope_constructors_file,
        }

    interfaces, dependencies, bindings_derived_sources, global_constructors, event_names = parse_idl_files(main_idl_files, support_idl_files, global_constructors_filenames)

    write_dependency_file(options.interface_dependencies_file, dependencies, only_if_changed)
    write_dependency_file(options.bindings_derived_sources_file, bindings_derived_sources, only_if_changed)
    for interface_name, filename in global_constructors_filenames.iteritems():
        if interface_name in interfaces:
            generate_global_constructors_partial_interface(interface_name, filename, global_constructors[interface_name], only_if_changed)
    generate_event_names_file(options.event_names_file, event_names, only_if_changed)


if __name__ == '__main__':
    main()