Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320)
[chromium-blink-merge.git] / build / android / gyp / java_cpp_enum.py
blob16039599ba7aa73ff07b35a96ad5ca0222f48d29
1 #!/usr/bin/env python
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.
7 import collections
8 import re
9 import optparse
10 import os
11 from string import Template
12 import sys
14 from util import build_utils
16 # List of C++ types that are compatible with the Java code generated by this
17 # script.
18 ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
19 'short', 'unsigned short',
20 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
22 class EnumDefinition(object):
23 def __init__(self, original_enum_name=None, class_name_override=None,
24 enum_package=None, entries=None, fixed_type=None):
25 self.original_enum_name = original_enum_name
26 self.class_name_override = class_name_override
27 self.enum_package = enum_package
28 self.entries = collections.OrderedDict(entries or [])
29 self.prefix_to_strip = None
30 self.fixed_type = fixed_type
32 def AppendEntry(self, key, value):
33 if key in self.entries:
34 raise Exception('Multiple definitions of key %s found.' % key)
35 self.entries[key] = value
37 @property
38 def class_name(self):
39 return self.class_name_override or self.original_enum_name
41 def Finalize(self):
42 self._Validate()
43 self._AssignEntryIndices()
44 self._StripPrefix()
46 def _Validate(self):
47 assert self.class_name
48 assert self.enum_package
49 assert self.entries
50 if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
51 raise Exception('Fixed type %s for enum %s not whitelisted.' %
52 (self.fixed_type, self.class_name))
54 def _AssignEntryIndices(self):
55 # Enums, if given no value, are given the value of the previous enum + 1.
56 if not all(self.entries.values()):
57 prev_enum_value = -1
58 for key, value in self.entries.iteritems():
59 if not value:
60 self.entries[key] = prev_enum_value + 1
61 elif value in self.entries:
62 self.entries[key] = self.entries[value]
63 else:
64 try:
65 self.entries[key] = int(value)
66 except ValueError:
67 raise Exception('Could not interpret integer from enum value "%s" '
68 'for key %s.' % (value, key))
69 prev_enum_value = self.entries[key]
72 def _StripPrefix(self):
73 prefix_to_strip = self.prefix_to_strip
74 if not prefix_to_strip:
75 prefix_to_strip = self.original_enum_name
76 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper()
77 prefix_to_strip += '_'
78 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
79 prefix_to_strip = ''
81 entries = collections.OrderedDict()
82 for (k, v) in self.entries.iteritems():
83 stripped_key = k.replace(prefix_to_strip, '', 1)
84 if isinstance(v, basestring):
85 stripped_value = v.replace(prefix_to_strip, '', 1)
86 else:
87 stripped_value = v
88 entries[stripped_key] = stripped_value
90 self.entries = entries
92 class DirectiveSet(object):
93 class_name_override_key = 'CLASS_NAME_OVERRIDE'
94 enum_package_key = 'ENUM_PACKAGE'
95 prefix_to_strip_key = 'PREFIX_TO_STRIP'
97 known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
99 def __init__(self):
100 self._directives = {}
102 def Update(self, key, value):
103 if key not in DirectiveSet.known_keys:
104 raise Exception("Unknown directive: " + key)
105 self._directives[key] = value
107 @property
108 def empty(self):
109 return len(self._directives) == 0
111 def UpdateDefinition(self, definition):
112 definition.class_name_override = self._directives.get(
113 DirectiveSet.class_name_override_key, '')
114 definition.enum_package = self._directives.get(
115 DirectiveSet.enum_package_key)
116 definition.prefix_to_strip = self._directives.get(
117 DirectiveSet.prefix_to_strip_key)
120 class HeaderParser(object):
121 single_line_comment_re = re.compile(r'\s*//')
122 multi_line_comment_start_re = re.compile(r'\s*/\*')
123 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
124 enum_end_re = re.compile(r'^\s*}\s*;\.*$')
125 generator_directive_re = re.compile(
126 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
127 multi_line_generator_directive_start_re = re.compile(
128 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
129 multi_line_directive_continuation_re = re.compile(
130 r'^\s*//\s+([\.\w]+)$')
131 multi_line_directive_end_re = re.compile(
132 r'^\s*//\s+([\.\w]*)\)$')
134 optional_class_or_struct_re = r'(class|struct)?'
135 enum_name_re = r'(\w+)'
136 optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
137 enum_start_re = re.compile(r'^\s*enum\s+' + optional_class_or_struct_re +
138 '\s*' + enum_name_re + '\s*' + optional_fixed_type_re + '\s*{\s*$')
140 def __init__(self, lines, path=None):
141 self._lines = lines
142 self._path = path
143 self._enum_definitions = []
144 self._in_enum = False
145 self._current_definition = None
146 self._generator_directives = DirectiveSet()
147 self._multi_line_generator_directive = None
149 def _ApplyGeneratorDirectives(self):
150 self._generator_directives.UpdateDefinition(self._current_definition)
151 self._generator_directives = DirectiveSet()
153 def ParseDefinitions(self):
154 for line in self._lines:
155 self._ParseLine(line)
156 return self._enum_definitions
158 def _ParseLine(self, line):
159 if self._multi_line_generator_directive:
160 self._ParseMultiLineDirectiveLine(line)
161 elif not self._in_enum:
162 self._ParseRegularLine(line)
163 else:
164 self._ParseEnumLine(line)
166 def _ParseEnumLine(self, line):
167 if HeaderParser.single_line_comment_re.match(line):
168 return
169 if HeaderParser.multi_line_comment_start_re.match(line):
170 raise Exception('Multi-line comments in enums are not supported in ' +
171 self._path)
172 enum_end = HeaderParser.enum_end_re.match(line)
173 enum_entry = HeaderParser.enum_line_re.match(line)
174 if enum_end:
175 self._ApplyGeneratorDirectives()
176 self._current_definition.Finalize()
177 self._enum_definitions.append(self._current_definition)
178 self._in_enum = False
179 elif enum_entry:
180 enum_key = enum_entry.groups()[0]
181 enum_value = enum_entry.groups()[2]
182 self._current_definition.AppendEntry(enum_key, enum_value)
184 def _ParseMultiLineDirectiveLine(self, line):
185 multi_line_directive_continuation = (
186 HeaderParser.multi_line_directive_continuation_re.match(line))
187 multi_line_directive_end = (
188 HeaderParser.multi_line_directive_end_re.match(line))
190 if multi_line_directive_continuation:
191 value_cont = multi_line_directive_continuation.groups()[0]
192 self._multi_line_generator_directive[1].append(value_cont)
193 elif multi_line_directive_end:
194 directive_name = self._multi_line_generator_directive[0]
195 directive_value = "".join(self._multi_line_generator_directive[1])
196 directive_value += multi_line_directive_end.groups()[0]
197 self._multi_line_generator_directive = None
198 self._generator_directives.Update(directive_name, directive_value)
199 else:
200 raise Exception('Malformed multi-line directive declaration in ' +
201 self._path)
203 def _ParseRegularLine(self, line):
204 enum_start = HeaderParser.enum_start_re.match(line)
205 generator_directive = HeaderParser.generator_directive_re.match(line)
206 multi_line_generator_directive_start = (
207 HeaderParser.multi_line_generator_directive_start_re.match(line))
209 if generator_directive:
210 directive_name = generator_directive.groups()[0]
211 directive_value = generator_directive.groups()[1]
212 self._generator_directives.Update(directive_name, directive_value)
213 elif multi_line_generator_directive_start:
214 directive_name = multi_line_generator_directive_start.groups()[0]
215 directive_value = multi_line_generator_directive_start.groups()[1]
216 self._multi_line_generator_directive = (directive_name, [directive_value])
217 elif enum_start:
218 if self._generator_directives.empty:
219 return
220 self._current_definition = EnumDefinition(
221 original_enum_name=enum_start.groups()[1],
222 fixed_type=enum_start.groups()[3])
223 self._in_enum = True
225 def GetScriptName():
226 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
227 build_index = script_components.index('build')
228 return os.sep.join(script_components[build_index:])
231 def DoGenerate(output_dir, source_paths, print_output_only=False):
232 output_paths = []
233 for source_path in source_paths:
234 enum_definitions = DoParseHeaderFile(source_path)
235 if not enum_definitions:
236 raise Exception('No enums found in %s\n'
237 'Did you forget prefixing enums with '
238 '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' %
239 source_path)
240 for enum_definition in enum_definitions:
241 package_path = enum_definition.enum_package.replace('.', os.path.sep)
242 file_name = enum_definition.class_name + '.java'
243 output_path = os.path.join(output_dir, package_path, file_name)
244 output_paths.append(output_path)
245 if not print_output_only:
246 build_utils.MakeDirectory(os.path.dirname(output_path))
247 DoWriteOutput(source_path, output_path, enum_definition)
248 return output_paths
251 def DoParseHeaderFile(path):
252 with open(path) as f:
253 return HeaderParser(f.readlines(), path).ParseDefinitions()
256 def GenerateOutput(source_path, enum_definition):
257 template = Template("""
258 // Copyright 2014 The Chromium Authors. All rights reserved.
259 // Use of this source code is governed by a BSD-style license that can be
260 // found in the LICENSE file.
262 // This file is autogenerated by
263 // ${SCRIPT_NAME}
264 // From
265 // ${SOURCE_PATH}
267 package ${PACKAGE};
269 public class ${CLASS_NAME} {
270 ${ENUM_ENTRIES}
272 """)
274 enum_template = Template(' public static final int ${NAME} = ${VALUE};')
275 enum_entries_string = []
276 for enum_name, enum_value in enum_definition.entries.iteritems():
277 values = {
278 'NAME': enum_name,
279 'VALUE': enum_value,
281 enum_entries_string.append(enum_template.substitute(values))
282 enum_entries_string = '\n'.join(enum_entries_string)
284 values = {
285 'CLASS_NAME': enum_definition.class_name,
286 'ENUM_ENTRIES': enum_entries_string,
287 'PACKAGE': enum_definition.enum_package,
288 'SCRIPT_NAME': GetScriptName(),
289 'SOURCE_PATH': source_path,
291 return template.substitute(values)
294 def DoWriteOutput(source_path, output_path, enum_definition):
295 with open(output_path, 'w') as out_file:
296 out_file.write(GenerateOutput(source_path, enum_definition))
298 def AssertFilesList(output_paths, assert_files_list):
299 actual = set(output_paths)
300 expected = set(assert_files_list)
301 if not actual == expected:
302 need_to_add = list(actual - expected)
303 need_to_remove = list(expected - actual)
304 raise Exception('Output files list does not match expectations. Please '
305 'add %s and remove %s.' % (need_to_add, need_to_remove))
307 def DoMain(argv):
308 usage = 'usage: %prog [options] output_dir input_file(s)...'
309 parser = optparse.OptionParser(usage=usage)
311 parser.add_option('--assert_file', action="append", default=[],
312 dest="assert_files_list", help='Assert that the given '
313 'file is an output. There can be multiple occurrences of '
314 'this flag.')
315 parser.add_option('--print_output_only', help='Only print output paths.',
316 action='store_true')
317 parser.add_option('--verbose', help='Print more information.',
318 action='store_true')
320 options, args = parser.parse_args(argv)
321 if len(args) < 2:
322 parser.error('Need to specify output directory and at least one input file')
323 output_paths = DoGenerate(args[0], args[1:],
324 print_output_only=options.print_output_only)
326 if options.assert_files_list:
327 AssertFilesList(output_paths, options.assert_files_list)
329 if options.verbose:
330 print 'Output paths:'
331 print '\n'.join(output_paths)
333 return ' '.join(output_paths)
335 if __name__ == '__main__':
336 DoMain(sys.argv[1:])