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

Side by Side Diff: build/android/gyp/java_cpp_string.py

Issue 1664113002: C++->Java string constants auto-generator (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@contentsettingstype_enum_2_string
Patch Set: add tests Created 4 years, 10 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
« no previous file with comments | « no previous file | build/android/gyp/java_cpp_string_tests.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2014 The Chromium Authors. All rights reserved. 3 # Copyright 2016 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 import collections 7 import collections
8 from datetime import date 8 from datetime import date
9 import re 9 import re
10 import optparse 10 import optparse
11 import os 11 import os
12 from string import Template 12 from string import Template
13 import sys 13 import sys
14 import zipfile 14 import zipfile
15 15
16 from util import build_utils 16 from util import build_utils
17 17
18 # List of C++ types that are compatible with the Java code generated by this 18 class StringDefinition(object):
raymes 2016/02/16 00:58:21 I guess this is actually a list of strings. So per
lshang 2016/02/16 23:00:19 Done.
19 # script. 19 def __init__(self, class_name=None, string_package=None, entries=None):
20 # 20 self.class_name = class_name
21 # This script can parse .idl files however, at present it ignores special 21 self.string_package = string_package
22 # rules such as [cpp_enum_prefix_override="ax_attr"].
23 ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
24 'short', 'unsigned short',
25 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
26
27 class EnumDefinition(object):
28 def __init__(self, original_enum_name=None, class_name_override=None,
29 enum_package=None, entries=None, fixed_type=None):
30 self.original_enum_name = original_enum_name
31 self.class_name_override = class_name_override
32 self.enum_package = enum_package
33 self.entries = collections.OrderedDict(entries or []) 22 self.entries = collections.OrderedDict(entries or [])
34 self.prefix_to_strip = None
35 self.fixed_type = fixed_type
36 23
37 def AppendEntry(self, key, value): 24 def AppendEntry(self, key, value):
38 if key in self.entries: 25 if key in self.entries:
39 raise Exception('Multiple definitions of key %s found.' % key) 26 raise Exception('Multiple definitions of key %s found.' % key)
40 self.entries[key] = value 27 self.entries[key] = value
41 28
42 @property
43 def class_name(self):
44 return self.class_name_override or self.original_enum_name
45
46 def Finalize(self): 29 def Finalize(self):
47 self._Validate() 30 self._Validate()
48 self._AssignEntryIndices()
49 self._StripPrefix()
50 31
51 def _Validate(self): 32 def _Validate(self):
52 assert self.class_name 33 assert self.class_name
53 assert self.enum_package 34 assert self.string_package
54 assert self.entries 35 assert self.entries
55 if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
56 raise Exception('Fixed type %s for enum %s not whitelisted.' %
57 (self.fixed_type, self.class_name))
58
59 def _AssignEntryIndices(self):
60 # Enums, if given no value, are given the value of the previous enum + 1.
61 if not all(self.entries.values()):
62 prev_enum_value = -1
63 for key, value in self.entries.iteritems():
64 if not value:
65 self.entries[key] = prev_enum_value + 1
66 elif value in self.entries:
67 self.entries[key] = self.entries[value]
68 else:
69 try:
70 self.entries[key] = int(value)
71 except ValueError:
72 raise Exception('Could not interpret integer from enum value "%s" '
73 'for key %s.' % (value, key))
74 prev_enum_value = self.entries[key]
75 36
76 37
77 def _StripPrefix(self): 38 class DirectiveSet(object):
78 prefix_to_strip = self.prefix_to_strip 39 class_name_key = 'CLASS_NAME'
79 if not prefix_to_strip: 40 string_package_key = 'STRING_PACKAGE'
80 prefix_to_strip = self.original_enum_name
81 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper()
82 prefix_to_strip += '_'
83 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
84 prefix_to_strip = ''
85 41
86 entries = collections.OrderedDict() 42 known_keys = [class_name_key, string_package_key]
87 for (k, v) in self.entries.iteritems():
88 stripped_key = k.replace(prefix_to_strip, '', 1)
89 if isinstance(v, basestring):
90 stripped_value = v.replace(prefix_to_strip, '', 1)
91 else:
92 stripped_value = v
93 entries[stripped_key] = stripped_value
94
95 self.entries = entries
96
97 class DirectiveSet(object):
98 class_name_override_key = 'CLASS_NAME_OVERRIDE'
99 enum_package_key = 'ENUM_PACKAGE'
100 prefix_to_strip_key = 'PREFIX_TO_STRIP'
101
102 known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
103 43
104 def __init__(self): 44 def __init__(self):
105 self._directives = {} 45 self._directives = {}
106 46
107 def Update(self, key, value): 47 def Update(self, key, value):
108 if key not in DirectiveSet.known_keys: 48 if key not in DirectiveSet.known_keys:
109 raise Exception("Unknown directive: " + key) 49 raise Exception("Unknown directive: " + key)
110 self._directives[key] = value 50 self._directives[key] = value
111 51
112 @property 52 @property
113 def empty(self): 53 def empty(self):
114 return len(self._directives) == 0 54 return len(self._directives) == 0
115 55
116 def UpdateDefinition(self, definition): 56 def UpdateDefinition(self, definition):
117 definition.class_name_override = self._directives.get( 57 definition.class_name = self._directives.get(
118 DirectiveSet.class_name_override_key, '') 58 DirectiveSet.class_name_key)
119 definition.enum_package = self._directives.get( 59 definition.string_package = self._directives.get(
120 DirectiveSet.enum_package_key) 60 DirectiveSet.string_package_key)
121 definition.prefix_to_strip = self._directives.get(
122 DirectiveSet.prefix_to_strip_key)
123 61
124 62
125 class HeaderParser(object): 63 class HeaderParser(object):
raymes 2016/02/16 00:58:20 This is no longer parsing a header file, so perhap
lshang 2016/02/16 23:00:20 Done.
126 single_line_comment_re = re.compile(r'\s*//') 64 single_line_comment_re = re.compile(r'\s*//')
127 multi_line_comment_start_re = re.compile(r'\s*/\*') 65 multi_line_comment_start_re = re.compile(r'\s*/\*')
128 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?') 66 multi_line_comment_end_re = re.compile(r'\s*\*/')
129 enum_end_re = re.compile(r'^\s*}\s*;\.*$') 67 single_string_line_re = re.compile(
68 r'^\s*const\s*char\s*(\w+)\[\]\s*\=\s*([^,\n]+)?"([^"]*)";')
raymes 2016/02/16 00:58:21 We may not need ([^,\n]+)?
lshang 2016/02/16 23:00:19 Done.
69 multi_string_line_start_re = re.compile(
70 r'^\s*const\s*char\s*(\w+)\[\]\s*\=?\s*$')
71 multi_string_line_end_re = re.compile(
72 r'^\s*\=?\s*"([^"]*)";')
130 generator_directive_re = re.compile( 73 generator_directive_re = re.compile(
131 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$') 74 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
132 multi_line_generator_directive_start_re = re.compile( 75 multi_line_generator_directive_start_re = re.compile(
133 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$') 76 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
134 multi_line_directive_continuation_re = re.compile( 77 multi_line_directive_continuation_re = re.compile(
135 r'^\s*//\s+([\.\w]+)$') 78 r'^\s*//\s+([\.\w]+)$')
136 multi_line_directive_end_re = re.compile( 79 multi_line_directive_end_re = re.compile(
137 r'^\s*//\s+([\.\w]*)\)$') 80 r'^\s*//\s+([\.\w]*)\)$')
138 81
139 optional_class_or_struct_re = r'(class|struct)?'
140 enum_name_re = r'(\w+)'
141 optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
142 enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' +
143 optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' +
144 optional_fixed_type_re + '\s*{\s*$')
145 82
146 def __init__(self, lines, path=None): 83 def __init__(self, lines, path=None):
147 self._lines = lines 84 self._lines = lines
148 self._path = path 85 self._path = path
149 self._enum_definitions = [] 86 self._string_definitions = []
150 self._in_enum = False
151 self._current_definition = None 87 self._current_definition = None
152 self._generator_directives = DirectiveSet() 88 self._generator_directives = DirectiveSet()
153 self._multi_line_generator_directive = None 89 self._multi_line_generator_directive = None
90 self._current_key = None
raymes 2016/02/16 00:58:21 maybe call this: _current_multiline_string_key
lshang 2016/02/16 23:00:20 Done.
91 self._in_comment = False
154 92
155 def _ApplyGeneratorDirectives(self): 93 def _ApplyGeneratorDirectives(self):
156 self._generator_directives.UpdateDefinition(self._current_definition) 94 self._generator_directives.UpdateDefinition(self._current_definition)
157 self._generator_directives = DirectiveSet() 95 self._generator_directives = DirectiveSet()
158 96
159 def ParseDefinitions(self): 97 def ParseDefinitions(self):
98 self._current_definition = StringDefinition()
160 for line in self._lines: 99 for line in self._lines:
161 self._ParseLine(line) 100 self._ParseLine(line)
162 return self._enum_definitions 101 self._ApplyGeneratorDirectives()
102 self._current_definition.Finalize()
103 return self._current_definition
163 104
164 def _ParseLine(self, line): 105 def _ParseLine(self, line):
165 if self._multi_line_generator_directive: 106 if self._multi_line_generator_directive:
166 self._ParseMultiLineDirectiveLine(line) 107 self._ParseMultiLineDirectiveLine(line)
167 elif not self._in_enum: 108 elif self._in_comment:
168 self._ParseRegularLine(line) 109 self._ParseCommentLine(line)
169 else: 110 else:
170 self._ParseEnumLine(line) 111 self._ParseStringLine(line)
171 112
172 def _ParseEnumLine(self, line): 113 def _ParseStringLine(self, line):
114 generator_directive = HeaderParser.generator_directive_re.match(line)
115 multi_line_generator_directive_start = (
116 HeaderParser.multi_line_generator_directive_start_re.match(line))
117 if generator_directive:
118 directive_name = generator_directive.groups()[0]
119 directive_value = generator_directive.groups()[1]
120 self._generator_directives.Update(directive_name, directive_value)
121 return
122 elif multi_line_generator_directive_start:
123 directive_name = multi_line_generator_directive_start.groups()[0]
124 directive_value = multi_line_generator_directive_start.groups()[1]
125 self._multi_line_generator_directive = (directive_name, [directive_value])
126 return
127
173 if HeaderParser.single_line_comment_re.match(line): 128 if HeaderParser.single_line_comment_re.match(line):
174 return 129 return
175 if HeaderParser.multi_line_comment_start_re.match(line): 130 if HeaderParser.multi_line_comment_start_re.match(line):
176 raise Exception('Multi-line comments in enums are not supported in ' + 131 self._in_comment = True
raymes 2016/02/16 00:58:21 Could we just ignore multi line comments, as in th
lshang 2016/02/16 23:00:19 Done.
177 self._path) 132 return
178 enum_end = HeaderParser.enum_end_re.match(line) 133
179 enum_entry = HeaderParser.enum_line_re.match(line) 134 string_multi_line_start = (
180 if enum_end: 135 HeaderParser.multi_string_line_start_re.match(line))
181 self._ApplyGeneratorDirectives() 136 single_string_line = HeaderParser.single_string_line_re.match(line)
182 self._current_definition.Finalize() 137 if string_multi_line_start or single_string_line:
183 self._enum_definitions.append(self._current_definition) 138 if self._generator_directives.empty:
184 self._in_enum = False 139 return
185 elif enum_entry: 140 if string_multi_line_start:
186 enum_key = enum_entry.groups()[0] 141 self._current_key = string_multi_line_start.groups()[0]
187 enum_value = enum_entry.groups()[2] 142 return
188 self._current_definition.AppendEntry(enum_key, enum_value) 143 elif single_string_line:
144 string_key = single_string_line.groups()[0]
145 string_value = single_string_line.groups()[2]
146 self._current_definition.AppendEntry(string_key, string_value)
147 return
148
149 string_multi_line_end = HeaderParser.multi_string_line_end_re.match(line)
150 if string_multi_line_end:
151 string_key = self._current_key
raymes 2016/02/16 00:58:21 Maybe a slightly different approach would be safer
lshang 2016/02/16 23:00:20 Done.
152 string_value = string_multi_line_end.groups()[0]
153 self._current_definition.AppendEntry(string_key, string_value)
154 return
155
189 156
190 def _ParseMultiLineDirectiveLine(self, line): 157 def _ParseMultiLineDirectiveLine(self, line):
191 multi_line_directive_continuation = ( 158 multi_line_directive_continuation = (
192 HeaderParser.multi_line_directive_continuation_re.match(line)) 159 HeaderParser.multi_line_directive_continuation_re.match(line))
193 multi_line_directive_end = ( 160 multi_line_directive_end = (
194 HeaderParser.multi_line_directive_end_re.match(line)) 161 HeaderParser.multi_line_directive_end_re.match(line))
195 162
196 if multi_line_directive_continuation: 163 if multi_line_directive_continuation:
197 value_cont = multi_line_directive_continuation.groups()[0] 164 value_cont = multi_line_directive_continuation.groups()[0]
198 self._multi_line_generator_directive[1].append(value_cont) 165 self._multi_line_generator_directive[1].append(value_cont)
199 elif multi_line_directive_end: 166 elif multi_line_directive_end:
200 directive_name = self._multi_line_generator_directive[0] 167 directive_name = self._multi_line_generator_directive[0]
201 directive_value = "".join(self._multi_line_generator_directive[1]) 168 directive_value = "".join(self._multi_line_generator_directive[1])
202 directive_value += multi_line_directive_end.groups()[0] 169 directive_value += multi_line_directive_end.groups()[0]
203 self._multi_line_generator_directive = None 170 self._multi_line_generator_directive = None
204 self._generator_directives.Update(directive_name, directive_value) 171 self._generator_directives.Update(directive_name, directive_value)
205 else: 172 else:
206 raise Exception('Malformed multi-line directive declaration in ' + 173 raise Exception('Malformed multi-line directive declaration in ' +
207 self._path) 174 self._path)
208 175
209 def _ParseRegularLine(self, line): 176 def _ParseCommentLine(self, line):
210 enum_start = HeaderParser.enum_start_re.match(line) 177 multi_line_comment_end = HeaderParser.multi_line_comment_end_re.match(line)
211 generator_directive = HeaderParser.generator_directive_re.match(line)
212 multi_line_generator_directive_start = (
213 HeaderParser.multi_line_generator_directive_start_re.match(line))
214 178
215 if generator_directive: 179 if multi_line_comment_end:
216 directive_name = generator_directive.groups()[0] 180 self._in_comment = False
217 directive_value = generator_directive.groups()[1] 181 return
218 self._generator_directives.Update(directive_name, directive_value) 182
219 elif multi_line_generator_directive_start:
220 directive_name = multi_line_generator_directive_start.groups()[0]
221 directive_value = multi_line_generator_directive_start.groups()[1]
222 self._multi_line_generator_directive = (directive_name, [directive_value])
223 elif enum_start:
224 if self._generator_directives.empty:
225 return
226 self._current_definition = EnumDefinition(
227 original_enum_name=enum_start.groups()[1],
228 fixed_type=enum_start.groups()[3])
229 self._in_enum = True
230 183
231 def GetScriptName(): 184 def GetScriptName():
232 return os.path.basename(os.path.abspath(sys.argv[0])) 185 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
186 build_index = script_components.index('build')
187 return os.sep.join(script_components[build_index:])
raymes 2016/02/16 00:58:21 Hmm I'm not sure I understand why this changed?
lshang 2016/02/16 23:00:19 Done. java_cpp_enum.py changed this on 2016-01-12,
233 188
234 def DoGenerate(source_paths): 189 def DoGenerate(source_paths):
235 for source_path in source_paths: 190 for source_path in source_paths:
236 enum_definitions = DoParseHeaderFile(source_path) 191 string_definition = DoParseHeaderFile(source_path)
237 if not enum_definitions: 192 if not string_definition:
238 raise Exception('No enums found in %s\n' 193 raise Exception('No strings found in %s\n'
239 'Did you forget prefixing enums with ' 194 'Did you forget prefixing strings with '
240 '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' % 195 '"// GENERATED_JAVA_STRING_PACKAGE: foo"?' %
241 source_path) 196 source_path)
242 for enum_definition in enum_definitions: 197 #for string_definition in string_definitions:
243 package_path = enum_definition.enum_package.replace('.', os.path.sep) 198 package_path = string_definition.string_package.replace('.', os.path.sep)
244 file_name = enum_definition.class_name + '.java' 199 file_name = string_definition.class_name + '.java'
245 output_path = os.path.join(package_path, file_name) 200 output_path = os.path.join(package_path, file_name)
246 output = GenerateOutput(source_path, enum_definition) 201 output = GenerateOutput(source_path, string_definition)
247 yield output_path, output 202 yield output_path, output
248
249 203
250 def DoParseHeaderFile(path): 204 def DoParseHeaderFile(path):
251 with open(path) as f: 205 with open(path) as f:
252 return HeaderParser(f.readlines(), path).ParseDefinitions() 206 return HeaderParser(f.readlines(), path).ParseDefinitions()
253 207
254 208
255 def GenerateOutput(source_path, enum_definition): 209 def GenerateOutput(source_path, string_definition):
256 template = Template(""" 210 template = Template("""
257 // Copyright ${YEAR} The Chromium Authors. All rights reserved. 211 // Copyright ${YEAR} The Chromium Authors. All rights reserved.
258 // Use of this source code is governed by a BSD-style license that can be 212 // Use of this source code is governed by a BSD-style license that can be
259 // found in the LICENSE file. 213 // found in the LICENSE file.
260 214
261 // This file is autogenerated by 215 // This file is autogenerated by
262 // ${SCRIPT_NAME} 216 // ${SCRIPT_NAME}
263 // From 217 // From
264 // ${SOURCE_PATH} 218 // ${SOURCE_PATH}
265 219
266 package ${PACKAGE}; 220 package ${PACKAGE};
267 221
268 public class ${CLASS_NAME} { 222 public class ${CLASS_NAME} {
269 ${ENUM_ENTRIES} 223 ${STRING_ENTRIES}
270 } 224 }
271 """) 225 """)
272 226
273 enum_template = Template(' public static final int ${NAME} = ${VALUE};') 227 string_template = (
274 enum_entries_string = [] 228 Template(' public static final String ${NAME} = "${VALUE}";'))
275 for enum_name, enum_value in enum_definition.entries.iteritems(): 229 string_entries_string = []
230 for string_name, string_value in string_definition.entries.iteritems():
276 values = { 231 values = {
277 'NAME': enum_name, 232 'NAME': string_name,
278 'VALUE': enum_value, 233 'VALUE': string_value,
279 } 234 }
280 enum_entries_string.append(enum_template.substitute(values)) 235 string_entries_string.append(string_template.substitute(values))
281 enum_entries_string = '\n'.join(enum_entries_string) 236 string_entries_string = '\n'.join(string_entries_string)
282 237
283 values = { 238 values = {
284 'CLASS_NAME': enum_definition.class_name, 239 'CLASS_NAME': string_definition.class_name,
285 'ENUM_ENTRIES': enum_entries_string, 240 'STRING_ENTRIES': string_entries_string,
286 'PACKAGE': enum_definition.enum_package, 241 'PACKAGE': string_definition.string_package,
287 'SCRIPT_NAME': GetScriptName(), 242 'SCRIPT_NAME': GetScriptName(),
288 'SOURCE_PATH': source_path, 243 'SOURCE_PATH': source_path,
289 'YEAR': str(date.today().year) 244 'YEAR': str(date.today().year)
290 } 245 }
291 return template.substitute(values) 246 return template.substitute(values)
292 247
293 248
294 def AssertFilesList(output_paths, assert_files_list): 249 def AssertFilesList(output_paths, assert_files_list):
295 actual = set(output_paths) 250 actual = set(output_paths)
296 expected = set(assert_files_list) 251 expected = set(assert_files_list)
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 if options.assert_files_list: 312 if options.assert_files_list:
358 AssertFilesList(output_paths, options.assert_files_list) 313 AssertFilesList(output_paths, options.assert_files_list)
359 314
360 if options.verbose: 315 if options.verbose:
361 print 'Output paths:' 316 print 'Output paths:'
362 print '\n'.join(output_paths) 317 print '\n'.join(output_paths)
363 318
364 # Used by GYP. 319 # Used by GYP.
365 return ' '.join(output_paths) 320 return ' '.join(output_paths)
366 321
367
368 if __name__ == '__main__': 322 if __name__ == '__main__':
369 DoMain(sys.argv[1:]) 323 DoMain(sys.argv[1:])
OLDNEW
« no previous file with comments | « no previous file | build/android/gyp/java_cpp_string_tests.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698