OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Utility functions (file reading, simple IDL parsing by regexes) for IDL build
. | 5 """Utility functions (file reading, simple IDL parsing by regexes) for IDL build
. |
6 | 6 |
7 Design doc: http://www.chromium.org/developers/design-documents/idl-build | 7 Design doc: http://www.chromium.org/developers/design-documents/idl-build |
8 """ | 8 """ |
9 | 9 |
10 import os | 10 import os |
11 import cPickle as pickle | 11 import cPickle as pickle |
12 import re | 12 import re |
13 import string | 13 import string |
14 import subprocess | 14 import subprocess |
15 | 15 |
16 | 16 |
17 KNOWN_COMPONENTS = frozenset(['core', 'modules']) | 17 KNOWN_COMPONENTS = frozenset(['core', 'modules']) |
18 | 18 KNOWN_COMPONENTS_WITH_TESTING = frozenset(['core', 'modules', 'testing']) |
19 | |
20 class IdlBadFilenameError(Exception): | |
21 """Raised if an IDL filename disagrees with the interface name in the file."
"" | |
22 pass | |
23 | 19 |
24 | 20 |
25 def idl_filename_to_interface_name(idl_filename): | 21 def idl_filename_to_interface_name(idl_filename): |
26 # interface name is the root of the basename: InterfaceName.idl | 22 # interface name is the root of the basename: InterfaceName.idl |
27 return os.path.splitext(os.path.basename(idl_filename))[0] | 23 return os.path.splitext(os.path.basename(idl_filename))[0] |
28 | 24 |
29 | 25 |
30 def idl_filename_to_component(idl_filename): | 26 def idl_filename_to_component_with_known_components(idl_filename, known_componen
ts): |
31 path = os.path.dirname(os.path.realpath(idl_filename)) | 27 path = os.path.dirname(os.path.realpath(idl_filename)) |
32 while path: | 28 while path: |
33 dirname, basename = os.path.split(path) | 29 dirname, basename = os.path.split(path) |
34 if basename.lower() in KNOWN_COMPONENTS: | 30 if not basename: |
| 31 break |
| 32 if basename.lower() in known_components: |
35 return basename.lower() | 33 return basename.lower() |
36 path = dirname | 34 path = dirname |
37 raise 'Unknown component type for %s' % idl_filename | 35 raise Exception('Unknown component type for %s' % idl_filename) |
| 36 |
| 37 |
| 38 def idl_filename_to_component(idl_filename): |
| 39 return idl_filename_to_component_with_known_components(idl_filename, KNOWN_C
OMPONENTS) |
| 40 |
| 41 |
| 42 def is_testing_target(idl_filename): |
| 43 component = idl_filename_to_component_with_known_components(idl_filename, KN
OWN_COMPONENTS_WITH_TESTING) |
| 44 return component == 'testing' |
| 45 |
| 46 |
| 47 # See whether "component" can depend on "dependency" or not: |
| 48 # Suppose that we have interface X and Y: |
| 49 # - if X is a partial interface and Y is the original interface, |
| 50 # use is_valid_component_dependency(X, Y). |
| 51 # - if X implements Y, use is_valid_component_dependency(X, Y) |
| 52 # Suppose that X is a cpp file and Y is a header file: |
| 53 # - if X includes Y, use is_valid_component_dependency(X, Y) |
| 54 def is_valid_component_dependency(component, dependency): |
| 55 assert component in KNOWN_COMPONENTS |
| 56 assert dependency in KNOWN_COMPONENTS |
| 57 if component == 'core' and dependency == 'modules': |
| 58 return False |
| 59 return True |
| 60 |
| 61 |
| 62 class ComponentInfoProvider(object): |
| 63 """Base class of information provider which provides component-specific |
| 64 information. |
| 65 """ |
| 66 def __init__(self): |
| 67 pass |
| 68 |
| 69 @property |
| 70 def interfaces_info(self): |
| 71 return {} |
| 72 |
| 73 @property |
| 74 def component_info(self): |
| 75 return {} |
| 76 |
| 77 @property |
| 78 def enumerations(self): |
| 79 return {} |
| 80 |
| 81 @property |
| 82 def typedefs(self): |
| 83 return {} |
| 84 |
| 85 @property |
| 86 def union_types(self): |
| 87 return set() |
| 88 |
| 89 @property |
| 90 def include_path_for_union_types(self): |
| 91 return None |
| 92 |
| 93 |
| 94 class ComponentInfoProviderCore(ComponentInfoProvider): |
| 95 def __init__(self, interfaces_info, component_info): |
| 96 super(ComponentInfoProviderCore, self).__init__() |
| 97 self._interfaces_info = interfaces_info |
| 98 self._component_info = component_info |
| 99 |
| 100 @property |
| 101 def interfaces_info(self): |
| 102 return self._interfaces_info |
| 103 |
| 104 @property |
| 105 def component_info(self): |
| 106 return self._component_info |
| 107 |
| 108 @property |
| 109 def enumerations(self): |
| 110 return self._component_info['enumerations'] |
| 111 |
| 112 @property |
| 113 def typedefs(self): |
| 114 return self._component_info['typedefs'] |
| 115 |
| 116 @property |
| 117 def union_types(self): |
| 118 return self._component_info['union_types'] |
| 119 |
| 120 @property |
| 121 def include_path_for_union_types(self): |
| 122 return 'bindings/core/v8/UnionTypesCore.h' |
| 123 |
| 124 @property |
| 125 def specifier_for_export(self): |
| 126 return 'CORE_EXPORT ' |
| 127 |
| 128 @property |
| 129 def include_path_for_export(self): |
| 130 return 'core/CoreExport.h' |
| 131 |
| 132 |
| 133 class ComponentInfoProviderModules(ComponentInfoProvider): |
| 134 def __init__(self, interfaces_info, component_info_core, |
| 135 component_info_modules): |
| 136 super(ComponentInfoProviderModules, self).__init__() |
| 137 self._interfaces_info = interfaces_info |
| 138 self._component_info_core = component_info_core |
| 139 self._component_info_modules = component_info_modules |
| 140 |
| 141 @property |
| 142 def interfaces_info(self): |
| 143 return self._interfaces_info |
| 144 |
| 145 @property |
| 146 def component_info(self): |
| 147 return self._component_info_modules |
| 148 |
| 149 @property |
| 150 def enumerations(self): |
| 151 enums = self._component_info_core['enumerations'].copy() |
| 152 enums.update(self._component_info_modules['enumerations']) |
| 153 return enums |
| 154 |
| 155 @property |
| 156 def typedefs(self): |
| 157 typedefs = self._component_info_core['typedefs'].copy() |
| 158 typedefs.update(self._component_info_modules['typedefs']) |
| 159 return typedefs |
| 160 |
| 161 @property |
| 162 def union_types(self): |
| 163 # Remove duplicate union types from component_info_modules to avoid |
| 164 # generating multiple container generation. |
| 165 return self._component_info_modules['union_types'] - self._component_inf
o_core['union_types'] |
| 166 |
| 167 @property |
| 168 def include_path_for_union_types(self): |
| 169 return 'bindings/modules/v8/UnionTypesModules.h' |
| 170 |
| 171 @property |
| 172 def specifier_for_export(self): |
| 173 return 'MODULES_EXPORT ' |
| 174 |
| 175 @property |
| 176 def include_path_for_export(self): |
| 177 return 'modules/ModulesExport.h' |
| 178 |
| 179 |
| 180 def load_interfaces_info_overall_pickle(info_dir): |
| 181 with open(os.path.join(info_dir, 'modules', 'InterfacesInfoOverall.pickle'))
as interface_info_file: |
| 182 return pickle.load(interface_info_file) |
| 183 |
| 184 |
| 185 def create_component_info_provider_core(info_dir): |
| 186 interfaces_info = load_interfaces_info_overall_pickle(info_dir) |
| 187 with open(os.path.join(info_dir, 'core', 'ComponentInfoCore.pickle')) as com
ponent_info_file: |
| 188 component_info = pickle.load(component_info_file) |
| 189 return ComponentInfoProviderCore(interfaces_info, component_info) |
| 190 |
| 191 |
| 192 def create_component_info_provider_modules(info_dir): |
| 193 interfaces_info = load_interfaces_info_overall_pickle(info_dir) |
| 194 with open(os.path.join(info_dir, 'core', 'ComponentInfoCore.pickle')) as com
ponent_info_file: |
| 195 component_info_core = pickle.load(component_info_file) |
| 196 with open(os.path.join(info_dir, 'modules', 'ComponentInfoModules.pickle'))
as component_info_file: |
| 197 component_info_modules = pickle.load(component_info_file) |
| 198 return ComponentInfoProviderModules( |
| 199 interfaces_info, component_info_core, component_info_modules) |
| 200 |
| 201 |
| 202 def create_component_info_provider(info_dir, component): |
| 203 if component == 'core': |
| 204 return create_component_info_provider_core(info_dir) |
| 205 elif component == 'modules': |
| 206 return create_component_info_provider_modules(info_dir) |
| 207 else: |
| 208 return ComponentInfoProvider() |
38 | 209 |
39 | 210 |
40 ################################################################################ | 211 ################################################################################ |
41 # Basic file reading/writing | 212 # Basic file reading/writing |
42 ################################################################################ | 213 ################################################################################ |
43 | 214 |
44 def get_file_contents(filename): | 215 def get_file_contents(filename): |
45 with open(filename) as f: | 216 with open(filename) as f: |
46 return f.read() | 217 return f.read() |
47 | 218 |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
97 with open(destination_filename, 'w') as destination_file: | 268 with open(destination_filename, 'w') as destination_file: |
98 destination_file.write(new_text) | 269 destination_file.write(new_text) |
99 | 270 |
100 | 271 |
101 def write_pickle_file(pickle_filename, data, only_if_changed): | 272 def write_pickle_file(pickle_filename, data, only_if_changed): |
102 if only_if_changed and os.path.isfile(pickle_filename): | 273 if only_if_changed and os.path.isfile(pickle_filename): |
103 with open(pickle_filename) as pickle_file: | 274 with open(pickle_filename) as pickle_file: |
104 try: | 275 try: |
105 if pickle.load(pickle_file) == data: | 276 if pickle.load(pickle_file) == data: |
106 return | 277 return |
107 except (EOFError, pickle.UnpicklingError): | 278 except Exception: |
108 # If trouble unpickling, overwrite | 279 # If trouble unpickling, overwrite |
109 pass | 280 pass |
110 with open(pickle_filename, 'w') as pickle_file: | 281 with open(pickle_filename, 'w') as pickle_file: |
111 pickle.dump(data, pickle_file) | 282 pickle.dump(data, pickle_file) |
112 | 283 |
113 | 284 |
114 ################################################################################ | 285 ################################################################################ |
115 # IDL parsing | 286 # IDL parsing |
116 # | 287 # |
117 # We use regular expressions for parsing; this is incorrect (Web IDL is not a | 288 # We use regular expressions for parsing; this is incorrect (Web IDL is not a |
118 # regular language), but simple and sufficient in practice. | 289 # regular language), but simple and sufficient in practice. |
119 # Leading and trailing context (e.g. following '{') used to avoid false matches. | 290 # Leading and trailing context (e.g. following '{') used to avoid false matches. |
120 ################################################################################ | 291 ################################################################################ |
121 | 292 |
122 def get_partial_interface_name_from_idl(file_contents): | |
123 match = re.search(r'partial\s+interface\s+(\w+)\s*{', file_contents) | |
124 return match and match.group(1) | |
125 | |
126 | |
127 def get_implements_from_idl(file_contents, interface_name): | |
128 """Returns lists of implementing and implemented interfaces. | |
129 | |
130 Rule is: identifier-A implements identifier-B; | |
131 i.e., implement*ing* implements implement*ed*; | |
132 http://www.w3.org/TR/WebIDL/#idl-implements-statements | |
133 | |
134 Returns two lists of interfaces: identifier-As and identifier-Bs. | |
135 An 'implements' statements can be present in the IDL file for either the | |
136 implementing or the implemented interface, but not other files. | |
137 """ | |
138 implements_re = (r'^\s*' | |
139 r'(\w+)\s+' | |
140 r'implements\s+' | |
141 r'(\w+)\s*' | |
142 r';') | |
143 implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE) | |
144 implements_pairs = [match.groups() for match in implements_matches] | |
145 | |
146 foreign_implements = [pair for pair in implements_pairs | |
147 if interface_name not in pair] | |
148 if foreign_implements: | |
149 left, right = foreign_implements.pop() | |
150 raise IdlBadFilenameError( | |
151 'implements statement found in unrelated IDL file.\n' | |
152 'Statement is:\n' | |
153 ' %s implements %s;\n' | |
154 'but filename is unrelated "%s.idl"' % | |
155 (left, right, interface_name)) | |
156 | |
157 return ( | |
158 [left for left, right in implements_pairs if right == interface_name], | |
159 [right for left, right in implements_pairs if left == interface_name]) | |
160 | |
161 | |
162 def is_callback_interface_from_idl(file_contents): | 293 def is_callback_interface_from_idl(file_contents): |
163 match = re.search(r'callback\s+interface\s+\w+\s*{', file_contents) | 294 match = re.search(r'callback\s+interface\s+\w+\s*{', file_contents) |
164 return bool(match) | 295 return bool(match) |
165 | 296 |
166 | 297 |
167 def is_dictionary_from_idl(file_contents): | 298 def should_generate_impl_file_from_idl(file_contents): |
168 match = re.search(r'dictionary\s+\w+\s*{', file_contents) | 299 """True when a given IDL file contents could generate .h/.cpp files.""" |
| 300 # FIXME: This would be error-prone and we should use AST rather than |
| 301 # improving the regexp pattern. |
| 302 match = re.search(r'(interface|dictionary|exception)\s+\w+', file_contents) |
169 return bool(match) | 303 return bool(match) |
170 | 304 |
171 | 305 |
172 def get_parent_interface(file_contents): | 306 def match_interface_extended_attributes_from_idl(file_contents): |
173 match = re.search(r'interface\s+' | |
174 r'\w+\s*' | |
175 r':\s*(\w+)\s*' | |
176 r'{', | |
177 file_contents) | |
178 return match and match.group(1) | |
179 | |
180 | |
181 def get_interface_extended_attributes_from_idl(file_contents): | |
182 # Strip comments | 307 # Strip comments |
183 # re.compile needed b/c Python 2.6 doesn't support flags in re.sub | 308 # re.compile needed b/c Python 2.6 doesn't support flags in re.sub |
184 single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE) | 309 single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE) |
185 block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL) | 310 block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL) |
186 file_contents = re.sub(single_line_comment_re, '', file_contents) | 311 file_contents = re.sub(single_line_comment_re, '', file_contents) |
187 file_contents = re.sub(block_comment_re, '', file_contents) | 312 file_contents = re.sub(block_comment_re, '', file_contents) |
188 | 313 |
189 match = re.search(r'\[(.*)\]\s*' | 314 match = re.search(r'\[(.*)\]\s*' |
190 r'((callback|partial)\s+)?' | 315 r'((callback|partial)\s+)?' |
191 r'(interface|exception)\s+' | 316 r'(interface|exception)\s+' |
192 r'\w+\s*' | 317 r'\w+\s*' |
193 r'(:\s*\w+\s*)?' | 318 r'(:\s*\w+\s*)?' |
194 r'{', | 319 r'{', |
195 file_contents, flags=re.DOTALL) | 320 file_contents, flags=re.DOTALL) |
| 321 return match |
| 322 |
| 323 |
| 324 def get_interface_extended_attributes_from_idl(file_contents): |
| 325 match = match_interface_extended_attributes_from_idl(file_contents) |
196 if not match: | 326 if not match: |
197 return {} | 327 return {} |
198 | 328 |
199 extended_attributes_string = match.group(1) | 329 extended_attributes_string = match.group(1) |
200 extended_attributes = {} | 330 extended_attributes = {} |
201 # FIXME: this splitting is WRONG: it fails on extended attributes where list
s of | 331 # FIXME: this splitting is WRONG: it fails on extended attributes where list
s of |
202 # multiple values are used, which are seperated by a comma and a space. | 332 # multiple values are used, which are seperated by a comma and a space. |
203 parts = [extended_attribute.strip() | 333 parts = [extended_attribute.strip() |
204 for extended_attribute in re.split(',\s+', extended_attributes_stri
ng) | 334 for extended_attribute in re.split(',\s+', extended_attributes_stri
ng) |
205 # Discard empty parts, which may exist due to trailing comma | 335 # Discard empty parts, which may exist due to trailing comma |
206 if extended_attribute.strip()] | 336 if extended_attribute.strip()] |
207 for part in parts: | 337 for part in parts: |
208 name, _, value = map(string.strip, part.partition('=')) | 338 name, _, value = map(string.strip, part.partition('=')) |
209 extended_attributes[name] = value | 339 extended_attributes[name] = value |
210 return extended_attributes | 340 return extended_attributes |
211 | 341 |
212 | 342 |
213 def get_put_forward_interfaces_from_idl(file_contents): | 343 def get_interface_exposed_arguments(file_contents): |
214 put_forwards_pattern = (r'\[[^\]]*PutForwards=[^\]]*\]\s+' | 344 match = match_interface_extended_attributes_from_idl(file_contents) |
215 r'readonly\s+' | 345 if not match: |
216 r'attribute\s+' | 346 return None |
217 r'(\w+)') | 347 |
218 return sorted(set(match.group(1) | 348 extended_attributes_string = match.group(1) |
219 for match in re.finditer(put_forwards_pattern, | 349 match = re.search(r'[^=]\bExposed\(([^)]*)\)', file_contents) |
220 file_contents, | 350 if not match: |
221 flags=re.DOTALL))) | 351 return None |
| 352 arguments = [] |
| 353 for argument in map(string.strip, match.group(1).split(',')): |
| 354 exposed, runtime_enabled = argument.split() |
| 355 arguments.append({'exposed': exposed, 'runtime_enabled': runtime_enabled
}) |
| 356 |
| 357 return arguments |
OLD | NEW |