81838fe1865d5f819eaa9e7bdd41e2bd458584bc
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / bindings / scripts / generate_global_constructors.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Generates interface properties on global objects.
8
9 Concretely these are implemented as "constructor attributes", meaning
10 "attributes whose name ends with Constructor" (special-cased by code generator),
11 hence "global constructors" for short.
12
13 For reference on global objects, see:
14 http://heycam.github.io/webidl/#Global
15 http://heycam.github.io/webidl/#Exposed
16
17 Design document: http://www.chromium.org/developers/design-documents/idl-build
18 """
19
20 import itertools
21 import optparse
22 import os
23 import cPickle as pickle
24 import re
25 import sys
26
27 from collections import defaultdict
28 from utilities import get_file_contents, idl_filename_to_interface_name, read_file_to_list, write_file, get_interface_extended_attributes_from_idl, is_callback_interface_from_idl
29
30 interface_name_to_global_names = {}
31 global_name_to_constructors = defaultdict(list)
32
33
34 HEADER_FORMAT = """// Stub header file for {{idl_basename}}
35 // Required because the IDL compiler assumes that a corresponding header file
36 // exists for each IDL file.
37 """
38
39 def parse_options():
40     parser = optparse.OptionParser()
41     parser.add_option('--idl-files-list', help='file listing IDL files')
42     parser.add_option('--global-objects-file', help='pickle file of global objects')
43     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')
44
45     options, args = parser.parse_args()
46
47     if options.idl_files_list is None:
48         parser.error('Must specify a file listing IDL files using --idl-files-list.')
49     if options.global_objects_file is None:
50         parser.error('Must specify a pickle file of global objects using --global-objects-file.')
51     if options.write_file_only_if_changed is None:
52         parser.error('Must specify whether output files are only written if changed using --write-file-only-if-changed.')
53     options.write_file_only_if_changed = bool(options.write_file_only_if_changed)
54
55     return options, args
56
57
58 def flatten_list(iterable):
59     return list(itertools.chain.from_iterable(iterable))
60
61
62 def interface_name_to_constructors(interface_name):
63     """Returns constructors for an interface."""
64     global_names = interface_name_to_global_names[interface_name]
65     return flatten_list(global_name_to_constructors[global_name]
66                         for global_name in global_names)
67
68
69 def record_global_constructors(idl_filename):
70     interface_name = idl_filename_to_interface_name(idl_filename)
71     full_path = os.path.realpath(idl_filename)
72     idl_file_contents = get_file_contents(full_path)
73     extended_attributes = get_interface_extended_attributes_from_idl(idl_file_contents)
74
75     # An interface property is produced for every non-callback interface
76     # that does not have [NoInterfaceObject].
77     # Callback interfaces with constants also have interface properties,
78     # but there are none of these in Blink.
79     # http://heycam.github.io/webidl/#es-interfaces
80     if (is_callback_interface_from_idl(idl_file_contents) or
81         'NoInterfaceObject' in extended_attributes):
82         return
83
84     # The [Exposed] extended attribute MUST take an identifier list. Each
85     # identifier in the list MUST be a global name. An interface or interface
86     # member the extended attribute applies to will be exposed only on objects
87     # associated with ECMAScript global environments whose global object
88     # implements an interface that has a matching global name.
89     # FIXME: In spec names are comma-separated, but that makes parsing very
90     # difficult (https://www.w3.org/Bugs/Public/show_bug.cgi?id=24959).
91     exposed_global_names = extended_attributes.get('Exposed', 'Window').split('&')
92     new_constructors_list = generate_global_constructors_list(interface_name, extended_attributes)
93     for exposed_global_name in exposed_global_names:
94         global_name_to_constructors[exposed_global_name].extend(new_constructors_list)
95
96
97 def generate_global_constructors_list(interface_name, extended_attributes):
98     extended_attributes_list = [
99             name + '=' + extended_attributes[name]
100             for name in 'Conditional', 'PerContextEnabled', 'RuntimeEnabled'
101             if name in extended_attributes]
102     if extended_attributes_list:
103         extended_string = '[%s] ' % ', '.join(extended_attributes_list)
104     else:
105         extended_string = ''
106
107     attribute_string = 'attribute {interface_name}Constructor {interface_name}'.format(interface_name=interface_name)
108     attributes_list = [extended_string + attribute_string]
109
110     # In addition to the usual interface property, for every [NamedConstructor]
111     # extended attribute on an interface, a corresponding property MUST exist
112     # on the ECMAScript global object.
113     # http://heycam.github.io/webidl/#NamedConstructor
114     if 'NamedConstructor' in extended_attributes:
115         named_constructor = extended_attributes['NamedConstructor']
116         # Extract function name, namely everything before opening '('
117         constructor_name = re.sub(r'\(.*', '', named_constructor)
118         # Note the reduplicated 'ConstructorConstructor'
119         # FIXME: rename to NamedConstructor
120         attribute_string = 'attribute %sConstructorConstructor %s' % (interface_name, constructor_name)
121         attributes_list.append(extended_string + attribute_string)
122
123     return attributes_list
124
125
126 def write_global_constructors_partial_interface(interface_name, idl_filename, constructor_attributes_list, only_if_changed):
127     # FIXME: replace this with a simple Jinja template
128     lines = (['partial interface %s {\n' % interface_name] +
129              ['    %s;\n' % constructor_attribute
130               # FIXME: sort by interface name (not first by extended attributes)
131               for constructor_attribute in sorted(constructor_attributes_list)] +
132              ['};\n'])
133     write_file(''.join(lines), idl_filename, only_if_changed)
134     header_filename = os.path.splitext(idl_filename)[0] + '.h'
135     idl_basename = os.path.basename(idl_filename)
136     write_file(HEADER_FORMAT.format(idl_basename=idl_basename),
137                header_filename, only_if_changed)
138
139
140 ################################################################################
141
142 def main():
143     options, args = parse_options()
144
145     # Input IDL files are passed in a file, due to OS command line length
146     # limits. This is generated at GYP time, which is ok b/c files are static.
147     idl_files = read_file_to_list(options.idl_files_list)
148
149     # Output IDL files (to generate) are passed at the command line, since
150     # these are in the build directory, which is determined at build time, not
151     # GYP time.
152     # These are passed as pairs of GlobalObjectName, GlobalObject.idl
153     interface_name_idl_filename = [(args[i], args[i + 1])
154                                    for i in range(0, len(args), 2)]
155
156     with open(options.global_objects_file) as global_objects_file:
157         interface_name_to_global_names.update(pickle.load(global_objects_file))
158
159     for idl_filename in idl_files:
160         record_global_constructors(idl_filename)
161
162     # Check for [Exposed] / [Global] mismatch.
163     known_global_names = frozenset(itertools.chain.from_iterable(interface_name_to_global_names.values()))
164     exposed_global_names = frozenset(global_name_to_constructors)
165     if not exposed_global_names.issubset(known_global_names):
166         unknown_global_names = exposed_global_names.difference(known_global_names)
167         raise ValueError('The following global names were used in '
168                          '[Exposed=xxx] but do not match any [Global] / '
169                          '[PrimaryGlobal] interface: %s'
170                          % list(unknown_global_names))
171
172     # Write partial interfaces containing constructor attributes for each
173     # global interface.
174     for interface_name, idl_filename in interface_name_idl_filename:
175         constructors = interface_name_to_constructors(interface_name)
176         write_global_constructors_partial_interface(
177             interface_name,
178             idl_filename,
179             constructors,
180             options.write_file_only_if_changed)
181
182
183 if __name__ == '__main__':
184     sys.exit(main())