Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1076)

Side by Side Diff: Source/bindings/scripts/code_generator_v8.py

Issue 19607011: Generate binding code for VoidCallback.idl with code generator in python (Closed) Base URL: https://chromium.googlesource.com/chromium/blink@master
Patch Set: fix double -> single quote etc Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright (C) 2013 Google Inc. All rights reserved. 1 # Copyright (C) 2013 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the 11 # in the documentation and/or other materials provided with the
12 # distribution. 12 # distribution.
13 # * Neither the name of Google Inc. nor the names of its 13 # * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from 14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission. 15 # this software without specific prior written permission.
16 # 16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 28
29 """Generate Blink V8 bindings (.h and .cpp files). 29 """
30 Generate Blink V8 bindings (.h and .cpp files).
30 31
31 Input: An object of class IdlDefinitions, containing an IDL interface X 32 Input: An object of class IdlDefinitions, containing an IDL interface X
32 Output: V8X.h and V8X.cpp 33 Output: V8X.h and V8X.cpp
33
34 FIXME: Currently a stub, as part of landing the parser and code generator
35 incrementally. Only implements generation of dummy .cpp and .h files.
36 """ 34 """
37 35
38 import os.path 36 import os
37 import posixpath
38 import sys
39 import re
haraken 2013/07/26 02:03:30 Nit: Alphabetical order please.
40
41 import idl_definitions
42
43 # jinja2 is in chromium's third_party directory
44 module_path, module_name = os.path.split(__file__)
45 third_party = os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pard ir)
46 sys.path.append(third_party)
47 import jinja2
48
49
50 def apply_template(path_to_template, params):
haraken 2013/07/26 02:03:30 params => parameters Probably 'contents' might be
51 dirname, basename = os.path.split(path_to_template)
52 jinja_env = jinja2.Environment(trim_blocks=True, loader=jinja2.FileSystemLoa der([dirname]))
53 template = jinja_env.get_template(basename)
54 return template.render(params)
55
56
57 def get_v8_class_name(interface):
haraken 2013/07/26 02:03:30 get_v8_class_name => v8_class_name Omitting "get"
58 return 'V8' + interface.name
59
60
61 def get_impl_name(interface_or_function):
Nils Barth (inactive) 2013/07/25 11:58:20 This could just be: return interface_or_function.e
haraken 2013/07/26 02:03:30 Good point! You're a Python expert :) Other comme
kojih 2013/07/26 07:18:38 I agree. done.
62 implementedAs = interface_or_function.extended_attributes.get('ImplementedAs ')
63 if implementedAs is not None:
64 return implementedAs
65 return interface_or_function.name
66
67
68 def get_conditional_string(interface_or_attribute_or_operation):
haraken 2013/07/26 02:03:30 get_conditional_string => conditional_string
69 conditional = interface_or_attribute_or_operation.extended_attributes.get('C onditional')
70 if conditional is None:
Nils Barth (inactive) 2013/07/25 11:58:20 if not conditional: return '' ...and don't nee
71 return ''
72 else:
Nils Barth (inactive) 2013/07/25 11:58:20 No need for an else, since you've just returned.
73 operator = ''
74 if '&' in conditional:
Nils Barth (inactive) 2013/07/25 11:58:20 This seems clearer with a for loop (anyway, no nee
haraken 2013/07/26 02:03:30 Looks better!
kojih 2013/07/26 07:18:38 Thanks, done.
75 operator = '&'
76 if '|' in conditional:
77 operator = '|'
78 if operator == '':
79 return 'ENABLE(%s)' % conditional
80 else:
81 # Avoid duplicated conditions.
82 conditions = set(conditional.split(operator))
83 return (' %s%s ' % (operator, operator)).join(['ENABLE(%s)' % expres sion for expression in sorted(conditions)])
84
85
86 def sort_and_remove_duplicate(includes):
87 return sorted(list(set(includes)))
haraken 2013/07/26 02:03:30 How about assuming that includes is a set? There w
kojih 2013/07/26 07:18:38 Good idea! done.
88
89
90 class NativeTypeAndJSType:
haraken 2013/07/26 02:03:30 CPPTypeAndV8Type Actually I don't think this clas
91 def __init__(self, native_type=None, js_type=None):
92 self.native_type = native_type
93 self.js_type = js_type
94
95 # IDL data_type: [element's C++ data_type, V8 data_type]
haraken 2013/07/26 02:03:30 I'd remove this comment.
96 typed_arrays = {
97 'ArrayBuffer': NativeTypeAndJSType(),
98 'ArrayBufferView': NativeTypeAndJSType(),
haraken 2013/07/26 02:03:30 As far as I see the Perl code generator, these two
kojih 2013/07/26 07:18:38 ok.
99 'Uint8Array': NativeTypeAndJSType('unsigned char', 'v8::kExternalUnsignedByt eArray'),
100 'Uint8ClampedArray': NativeTypeAndJSType('unsigned char', 'v8::kExternalPixe lArray'),
101 'Uint16Array': NativeTypeAndJSType('unsigned short', 'v8::kExternalUnsignedS hortArray'),
102 'Uint32Array': NativeTypeAndJSType('unsigned int', 'v8::kExternalUnsignedInt Array'),
103 'Int8Array': NativeTypeAndJSType('signed char', 'v8::kExternalByteArray'),
104 'Int16Array': NativeTypeAndJSType('short', 'v8::kExternalShortArray'),
105 'Int32Array': NativeTypeAndJSType('int', 'v8::kExternalIntArray'),
106 'Float32Array': NativeTypeAndJSType('float', 'v8::kExternalFloatArray'),
107 'Float64Array': NativeTypeAndJSType('double', 'v8::kExternalDoubleArray'),
108 }
109
110 primitive_types = set([
haraken 2013/07/26 02:03:30 It looks like that methods and global variables ar
kojih 2013/07/26 07:18:38 ok, done.
111 'boolean',
112 'void',
113 'Date',
114 'byte',
115 'octet',
116 'short',
117 'long',
118 'long long',
119 'unsigned short',
120 'unsigned long',
121 'unsigned long long',
122 'float',
123 'double',
124 ])
125
126
127 def get_sequence_type(data_type):
haraken 2013/07/26 02:03:30 Drop this method in this CL.
128 matched = re.match(r'^sequence<([\w\s]+)>.*', data_type)
129 if matched:
130 return matched.group(1)
131 return None
132
133
134 def get_array_type(data_type):
haraken 2013/07/26 02:03:30 Ditto.
135 matched = re.match(r'^([\w\s]+)\[\]', data_type)
136 if matched:
137 return matched.group(1)
138 return None
139
140
141 def is_typed_array_type(data_type):
haraken 2013/07/26 02:03:30 Ditto.
142 return data_type in typed_arrays
143
144
145 def is_primitive_type(data_type):
146 return data_type in primitive_types
147
148
149 def is_union_type(data_type):
haraken 2013/07/26 02:03:30 Ditto.
150 return isinstance(data_type, idl_definitions.IdlUnionType)
151
152
153 def get_native_type(data_type):
haraken 2013/07/26 02:03:30 get_native_type => native_type
154 """
155 Return native data_type corresponds to IDL data_type.
Nils Barth (inactive) 2013/07/25 11:58:20 Syntax: no initial linebreak, but linebreak after
haraken 2013/07/26 02:03:30 native => cpp
156 @param[in] data_type ... IDL data_type
157 @param[in] called_by_webcore
158 @param[in] used_as_parameter
159 """
160 if data_type == 'boolean':
161 return 'bool'
162 raise Exception('Not supported')
163
164
165 def get_callback_parameter_declaration(operation):
haraken 2013/07/26 02:03:30 get_callback_parameter_declaration => callback_arg
kojih 2013/07/26 07:18:38 done.
166 parameters = ['%s %s' % (get_native_type(parameter.data_type), parameter.nam e) for parameter in operation.arguments]
167 return ', '.join(parameters)
168
169
170 class CodeGeneratorV8:
171 def __init__(self, definitions, interface_name, output_directory, idl_direct ories, verbose=False, generate_h=True, generate_cpp=True):
haraken 2013/07/26 02:03:30 Do we need generate_h and generate_cpp ? In case
Nils Barth (inactive) 2013/07/26 03:02:40 I was wondering the same thing. If we really want
kojih 2013/07/26 07:18:38 oh this was for rewriting work. (for example, gene
172 self.idl_definitions = definitions
173 self.interface_name = interface_name
174 self.idl_directories = idl_directories
175 self.output_directory = output_directory
176 self.generate_h = generate_h
177 self.generate_cpp = generate_cpp
178 self.verbose = verbose
179 self.header = ''
180 self.implementation = ''
haraken 2013/07/26 02:03:30 implementation => cpp
181 self.cached_interfaces = {}
haraken 2013/07/26 02:03:30 Drop this in this CL.
182 self.common_template_parameters = {}
183 self.interface = None
184 self.enum_types = dict([[enum.name, enum.values] for enum in self.idl_de finitions.enumerations.values()])
Nils Barth (inactive) 2013/07/25 11:58:20 This would probably be easier at the parser side.
haraken 2013/07/26 02:03:30 I'd drop this in this CL.
Nils Barth (inactive) 2013/07/26 03:02:40 Good point. Koji, no enums in these IDLs, right?
kojih 2013/07/26 07:18:38 exactly. removed.
185 self.callback_function_types = self.idl_definitions.callback_functions
haraken 2013/07/26 02:03:30 Shall we rename "function" to "method"? I think we
Nils Barth (inactive) 2013/07/26 03:02:40 This is an important general point: the parser str
kojih 2013/07/26 07:18:38 Yeah now this is unnecessary. This was primarily s
186
187 def is_enum_type(self, data_type):
haraken 2013/07/26 02:03:30 Drop this in this CL.
188 return data_type in self.enum_types
189
190 def is_callback_function_type(self, data_type):
191 return data_type in self.callback_function_types
192
193 def get_includes_for_type(self, data_type):
194 if self.skip_include_header(data_type):
195 return []
196 includes = []
haraken 2013/07/26 02:03:30 Don't you want to use a set for includes?
197 if data_type == 'EventListener':
198 includes.append('core/dom/EventListener.h')
199 elif data_type == 'SerializedScriptValue':
200 includes.append('bindings/v8/SerializedScriptValue.h')
201 elif data_type == 'any' or self.is_callback_function_type(data_type):
202 includes.append('bindings/v8/ScriptValue.h')
haraken 2013/07/26 02:03:30 Are these needed in this CL? Header includes in th
203 else:
204 includes.append('V8%s.h' % data_type)
205 return includes
206
207 def get_includes_for_parameter(self, parameter):
haraken 2013/07/26 02:03:30 parameter => argument The same comment to other p
208 includes = []
haraken 2013/07/26 02:03:30 Ditto. Can you use a set?
209 array_or_sequence_type = get_array_type(parameter.data_type) or get_sequ ence_type(parameter.data_type)
210 if array_or_sequence_type:
211 if self.is_ref_ptr_type(array_or_sequence_type):
haraken 2013/07/26 02:03:30 I don't understand what this branch is for. Maybe
212 includes += self.get_includes_for_type(array_or_sequence_type)
213 else:
214 includes += self.get_includes_for_type(parameter.data_type)
215 return includes
216
217 def is_ref_ptr_type(self, data_type):
218 return not(is_union_type(data_type) or
219 data_type == 'any' or data_type == 'DOMString' or
Nils Barth (inactive) 2013/07/25 11:58:20 A bit clearer to consistently have each on the sam
kojih 2013/07/26 07:18:38 I'll do that in the following patches. (this part
220 is_primitive_type(data_type) or
221 get_array_type(data_type) or
222 get_sequence_type(data_type) or
223 self.is_callback_function_type(data_type) or
224 self.is_enum_type(data_type))
225
226 def skip_include_header(self, data_type):
haraken 2013/07/26 02:03:30 skip_include_header => is_skip_include
227 return is_primitive_type(data_type) or \
228 self.is_enum_type(data_type) or \
Nils Barth (inactive) 2013/07/25 11:58:20 Style: use parentheses for implicit line continuat
kojih 2013/07/26 07:18:38 I'll do that in the following patches. (this part
229 self.is_callback_function_type(data_type) or \
230 data_type == 'DOMString'
231
232 def header_files_for_interface(self, interface_name, impl_class_name):
haraken 2013/07/26 02:03:30 get_includes_for_interface (for naming consistency
kojih 2013/07/26 07:18:38 Good point......... HeaderFilesForInterface actua
233 includes = []
haraken 2013/07/26 02:03:30 Can you use a set?
kojih 2013/07/26 07:18:38 done.
234 if is_typed_array_type(interface_name):
235 includes.append('wtf/%s.h' % interface_name)
236 elif not self.skip_include_header(interface_name):
237 # FIXME: parser will prepare posix form relative path from Source/bi ndings in IdlInterface.file_name
haraken 2013/07/26 02:03:30 Yeah, we should do that. nbarth: could you do tha
Nils Barth (inactive) 2013/07/26 03:02:40 Will do! It's a bit tricky to do properly, since w
238 idl_filename = self.idl_definitions.file_name
239 idl_rel_path_local = os.path.relpath(idl_filename)
240 idl_rel_path_posix = idl_rel_path_local.replace(os.path.sep, posixpa th.sep)
241
242 idl_dir_posix = posixpath.join('bindings', posixpath.dirname(idl_rel _path_posix))
243 includes.append(posixpath.join(idl_dir_posix, impl_class_name + '.h' ))
244 return includes
245
246 def generate_interface(self, interface):
247 self.interface = interface
248 self.common_template_parameters = {
haraken 2013/07/26 02:03:30 Nit: common_template_parameters => common_template
kojih 2013/07/26 07:18:38 done.
kojih 2013/07/26 07:18:38 done. replaced all template_parameters with templa
249 'conditional_string': get_conditional_string(self.interface),
250 }
251 if interface.is_callback:
252 template_parameters = self.common_template_parameters.copy()
253 template_parameters.update(self.generate_callback_template_parameter s())
254 if self.generate_h:
255 self.header = apply_template('templates/callback.h', template_pa rameters)
256 if self.generate_cpp:
257 self.implementation = apply_template('templates/callback.cpp', t emplate_parameters)
258 else:
259 if self.generate_h:
260 self.generate_header()
261 if self.generate_cpp:
262 self.generate_implementation()
263
264 def write_interface(self):
265 if self.interface_name in self.idl_definitions.interfaces:
266 interface = self.idl_definitions.interfaces[self.interface_name]
267 self.generate_interface(interface)
268 if self.generate_h:
269 header_filename = os.path.join(self.output_directory, get_v8_cla ss_name(self.interface) + '.h')
270 with open(header_filename, 'w') as f:
haraken 2013/07/26 02:03:30 Nit: f => file
Nils Barth (inactive) 2013/07/26 03:02:40 Even better, header_file For clarity, I've been co
kojih 2013/07/26 07:18:38 done. we can't use type, file as variable name in
271 f.write(self.header)
272 if self.generate_cpp:
273 implementation_filename = os.path.join(self.output_directory, ge t_v8_class_name(self.interface) + '.cpp')
haraken 2013/07/26 02:03:30 implementation_filename => cpp_filename
kojih 2013/07/26 07:18:38 done.
274 with open(implementation_filename, 'w') as f:
haraken 2013/07/26 02:03:30 Nit: f => file
Nils Barth (inactive) 2013/07/26 03:02:40 => cpp_file
275 f.write(self.implementation)
276 else:
277 raise Exception('%s not in IDL definitions' % self.interface_name)
haraken 2013/07/26 02:03:30 I'd prefer: if not self.interface_name in self.
Nils Barth (inactive) 2013/07/26 03:02:40 BTW: if key not in dict: ... ...is a bit clear
kojih 2013/07/26 07:18:38 done.
278
279 def generate_callback_template_parameters(self):
haraken 2013/07/26 02:03:30 generate_callback_template_parameters => generate_
280 impl_class_name = get_impl_name(self.interface)
281 v8_class_name = get_v8_class_name(self.interface)
282 functions = []
283 implementation_includes = [
haraken 2013/07/26 02:03:30 Can you use a set for the includes?
haraken 2013/07/26 02:03:30 implementation_includes => cpp_includes
284 'core/dom/ScriptExecutionContext.h',
285 'bindings/v8/V8Binding.h',
286 'bindings/v8/V8Callback.h',
287 'wtf/Assertions.h',
288 ]
289 header_includes = [
haraken 2013/07/26 02:03:30 Ditto.
290 'bindings/v8/ActiveDOMCallback.h',
291 'bindings/v8/DOMWrapperWorld.h',
292 'bindings/v8/ScopedPersistent.h',
293 ]
294 header_includes += self.header_files_for_interface(self.interface.name, impl_class_name)
295 for operation in self.interface.operations:
296 custom = 'Custom' in operation.extended_attributes
297 function = {}
298 if not custom:
haraken 2013/07/26 02:03:30 if not 'Custom' in operation.extended_attributes:
Nils Barth (inactive) 2013/07/26 03:02:40 Even better: if 'Custom' not in operation.extended
kojih 2013/07/26 07:18:38 done.
299 implementation_includes += self.get_includes_for_type(operation. data_type)
300 for parameter in operation.arguments:
301 implementation_includes += self.get_includes_for_parameter(p arameter)
302 if operation.data_type != 'boolean':
303 raise Exception('We don''t yet support callbacks that return non-boolean values.')
Nils Barth (inactive) 2013/07/25 11:58:20 BTW, it's fine to use "" for quotes if there are s
kojih 2013/07/26 07:18:38 done.
304 parameters = []
305 if len(operation.arguments) > 0:
Nils Barth (inactive) 2013/07/25 11:58:20 No need for > 0.
kojih 2013/07/26 07:18:38 done.
306 raise Exception('Not supported')
307 function = {
308 'return_type': get_native_type(operation.data_type),
309 'name': operation.name,
310 'parameter_declaration': get_callback_parameter_declaration( operation),
311 'prepare_js_parameters': '',
haraken 2013/07/26 02:03:30 Drop this in this CL.
312 'handles': ',\n'.join(['%sHandle' % parameter.name for param eter in operation.arguments]),
haraken 2013/07/26 02:03:30 You don't need to add "\n".
kojih 2013/07/26 07:18:38 ok, but dropped in this CL.
313 'parameters': parameters,
haraken 2013/07/26 02:03:30 Nit: parameters => arguments
kojih 2013/07/26 07:18:38 ok, but dropped in this CL.
314 'custom': custom,
315 }
316 functions.append(function)
317 implementation_includes = sort_and_remove_duplicate(implementation_inclu des)
318 header_includes = sort_and_remove_duplicate(header_includes)
319 template_parameters = {
haraken 2013/07/26 02:03:30 template_parameters => template_contents
320 'interface_name': self.interface.name,
321 'impl_class_name': impl_class_name,
322 'v8_class_name': v8_class_name,
323 'implementation_includes': implementation_includes,
324 'header_includes': header_includes,
haraken 2013/07/26 02:03:30 If header_includes is a set, you can just say: sor
325 'functions': functions,
haraken 2013/07/26 02:03:30 functions => methods As christophe commented in t
326 }
327 return template_parameters
328
329 def generate_header(self):
330 # TODO: implement
haraken 2013/07/26 02:03:30 TODO => FIXME
331 self.header = ''
332
333 def generate_implementation(self):
haraken 2013/07/26 02:03:30 generate_implementation => generate_cpp
334 # TODO: implement
haraken 2013/07/26 02:03:30 TODO => FIXME
335 self.implementation = ''
39 336
40 337
41 def generate_dummy_header_and_cpp(target_interface_name, output_directory): 338 def generate_dummy_header_and_cpp(target_interface_name, output_directory):
42 header_basename = 'V8%s.h' % target_interface_name 339 header_basename = 'V8%s.h' % target_interface_name
43 cpp_basename = 'V8%s.cpp' % target_interface_name 340 cpp_basename = 'V8%s.cpp' % target_interface_name
44 header_fullname = os.path.join(output_directory, header_basename) 341 header_fullname = os.path.join(output_directory, header_basename)
45 cpp_fullname = os.path.join(output_directory, cpp_basename) 342 cpp_fullname = os.path.join(output_directory, cpp_basename)
46 contents = """/* 343 contents = """/*
47 This file is generated just to tell build scripts that {header_basename} and 344 This file is generated just to tell build scripts that {header_basename} and
48 {cpp_basename} are created for {target_interface_name}.idl, and thus 345 {cpp_basename} are created for {target_interface_name}.idl, and thus
49 prevent the build scripts from trying to generate {header_basename} and 346 prevent the build scripts from trying to generate {header_basename} and
50 {cpp_basename} at every build. This file must not be tried to compile. 347 {cpp_basename} at every build. This file must not be tried to compile.
51 */ 348 */
52 """.format(**locals()) 349 """.format(**locals())
53 with open(header_fullname, 'w') as header_file: 350 with open(header_fullname, 'w') as header_file:
54 header_file.write(contents) 351 header_file.write(contents)
55 with open(cpp_fullname, 'w') as cpp_file: 352 with open(cpp_fullname, 'w') as cpp_file:
56 cpp_file.write(contents) 353 cpp_file.write(contents)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698