Move core/forwarders to internal.
[chromium-blink-merge.git] / build / android / gyp / java_cpp_enum.py
blobc2f1764b1be9ade87751aa7a8c8f4afa1f93abc6
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.
19 # This script can parse .idl files however, at present it ignores special
20 # rules such as [cpp_enum_prefix_override="ax_attr"].
21 ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
22 'short', 'unsigned short',
23 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
25 class EnumDefinition(object):
26 def __init__(self, original_enum_name=None, class_name_override=None,
27 enum_package=None, entries=None, fixed_type=None):
28 self.original_enum_name = original_enum_name
29 self.class_name_override = class_name_override
30 self.enum_package = enum_package
31 self.entries = collections.OrderedDict(entries or [])
32 self.prefix_to_strip = None
33 self.fixed_type = fixed_type
35 def AppendEntry(self, key, value):
36 if key in self.entries:
37 raise Exception('Multiple definitions of key %s found.' % key)
38 self.entries[key] = value
40 @property
41 def class_name(self):
42 return self.class_name_override or self.original_enum_name
44 def Finalize(self):
45 self._Validate()
46 self._AssignEntryIndices()
47 self._StripPrefix()
49 def _Validate(self):
50 assert self.class_name
51 assert self.enum_package
52 assert self.entries
53 if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
54 raise Exception('Fixed type %s for enum %s not whitelisted.' %
55 (self.fixed_type, self.class_name))
57 def _AssignEntryIndices(self):
58 # Enums, if given no value, are given the value of the previous enum + 1.
59 if not all(self.entries.values()):
60 prev_enum_value = -1
61 for key, value in self.entries.iteritems():
62 if not value:
63 self.entries[key] = prev_enum_value + 1
64 elif value in self.entries:
65 self.entries[key] = self.entries[value]
66 else:
67 try:
68 self.entries[key] = int(value)
69 except ValueError:
70 raise Exception('Could not interpret integer from enum value "%s" '
71 'for key %s.' % (value, key))
72 prev_enum_value = self.entries[key]
75 def _StripPrefix(self):
76 prefix_to_strip = self.prefix_to_strip
77 if not prefix_to_strip:
78 prefix_to_strip = self.original_enum_name
79 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper()
80 prefix_to_strip += '_'
81 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
82 prefix_to_strip = ''
84 entries = collections.OrderedDict()
85 for (k, v) in self.entries.iteritems():
86 stripped_key = k.replace(prefix_to_strip, '', 1)
87 if isinstance(v, basestring):
88 stripped_value = v.replace(prefix_to_strip, '', 1)
89 else:
90 stripped_value = v
91 entries[stripped_key] = stripped_value
93 self.entries = entries
95 class DirectiveSet(object):
96 class_name_override_key = 'CLASS_NAME_OVERRIDE'
97 enum_package_key = 'ENUM_PACKAGE'
98 prefix_to_strip_key = 'PREFIX_TO_STRIP'
100 known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
102 def __init__(self):
103 self._directives = {}
105 def Update(self, key, value):
106 if key not in DirectiveSet.known_keys:
107 raise Exception("Unknown directive: " + key)
108 self._directives[key] = value
110 @property
111 def empty(self):
112 return len(self._directives) == 0
114 def UpdateDefinition(self, definition):
115 definition.class_name_override = self._directives.get(
116 DirectiveSet.class_name_override_key, '')
117 definition.enum_package = self._directives.get(
118 DirectiveSet.enum_package_key)
119 definition.prefix_to_strip = self._directives.get(
120 DirectiveSet.prefix_to_strip_key)
123 class HeaderParser(object):
124 single_line_comment_re = re.compile(r'\s*//')
125 multi_line_comment_start_re = re.compile(r'\s*/\*')
126 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
127 enum_end_re = re.compile(r'^\s*}\s*;\.*$')
128 generator_directive_re = re.compile(
129 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
130 multi_line_generator_directive_start_re = re.compile(
131 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$')
132 multi_line_directive_continuation_re = re.compile(
133 r'^\s*//\s+([\.\w]+)$')
134 multi_line_directive_end_re = re.compile(
135 r'^\s*//\s+([\.\w]*)\)$')
137 optional_class_or_struct_re = r'(class|struct)?'
138 enum_name_re = r'(\w+)'
139 optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
140 enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' +
141 optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' +
142 optional_fixed_type_re + '\s*{\s*$')
144 def __init__(self, lines, path=None):
145 self._lines = lines
146 self._path = path
147 self._enum_definitions = []
148 self._in_enum = False
149 self._current_definition = None
150 self._generator_directives = DirectiveSet()
151 self._multi_line_generator_directive = None
153 def _ApplyGeneratorDirectives(self):
154 self._generator_directives.UpdateDefinition(self._current_definition)
155 self._generator_directives = DirectiveSet()
157 def ParseDefinitions(self):
158 for line in self._lines:
159 self._ParseLine(line)
160 return self._enum_definitions
162 def _ParseLine(self, line):
163 if self._multi_line_generator_directive:
164 self._ParseMultiLineDirectiveLine(line)
165 elif not self._in_enum:
166 self._ParseRegularLine(line)
167 else:
168 self._ParseEnumLine(line)
170 def _ParseEnumLine(self, line):
171 if HeaderParser.single_line_comment_re.match(line):
172 return
173 if HeaderParser.multi_line_comment_start_re.match(line):
174 raise Exception('Multi-line comments in enums are not supported in ' +
175 self._path)
176 enum_end = HeaderParser.enum_end_re.match(line)
177 enum_entry = HeaderParser.enum_line_re.match(line)
178 if enum_end:
179 self._ApplyGeneratorDirectives()
180 self._current_definition.Finalize()
181 self._enum_definitions.append(self._current_definition)
182 self._in_enum = False
183 elif enum_entry:
184 enum_key = enum_entry.groups()[0]
185 enum_value = enum_entry.groups()[2]
186 self._current_definition.AppendEntry(enum_key, enum_value)
188 def _ParseMultiLineDirectiveLine(self, line):
189 multi_line_directive_continuation = (
190 HeaderParser.multi_line_directive_continuation_re.match(line))
191 multi_line_directive_end = (
192 HeaderParser.multi_line_directive_end_re.match(line))
194 if multi_line_directive_continuation:
195 value_cont = multi_line_directive_continuation.groups()[0]
196 self._multi_line_generator_directive[1].append(value_cont)
197 elif multi_line_directive_end:
198 directive_name = self._multi_line_generator_directive[0]
199 directive_value = "".join(self._multi_line_generator_directive[1])
200 directive_value += multi_line_directive_end.groups()[0]
201 self._multi_line_generator_directive = None
202 self._generator_directives.Update(directive_name, directive_value)
203 else:
204 raise Exception('Malformed multi-line directive declaration in ' +
205 self._path)
207 def _ParseRegularLine(self, line):
208 enum_start = HeaderParser.enum_start_re.match(line)
209 generator_directive = HeaderParser.generator_directive_re.match(line)
210 multi_line_generator_directive_start = (
211 HeaderParser.multi_line_generator_directive_start_re.match(line))
213 if generator_directive:
214 directive_name = generator_directive.groups()[0]
215 directive_value = generator_directive.groups()[1]
216 self._generator_directives.Update(directive_name, directive_value)
217 elif multi_line_generator_directive_start:
218 directive_name = multi_line_generator_directive_start.groups()[0]
219 directive_value = multi_line_generator_directive_start.groups()[1]
220 self._multi_line_generator_directive = (directive_name, [directive_value])
221 elif enum_start:
222 if self._generator_directives.empty:
223 return
224 self._current_definition = EnumDefinition(
225 original_enum_name=enum_start.groups()[1],
226 fixed_type=enum_start.groups()[3])
227 self._in_enum = True
229 def GetScriptName():
230 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
231 build_index = script_components.index('build')
232 return os.sep.join(script_components[build_index:])
235 def DoGenerate(output_dir, source_paths, print_output_only=False):
236 output_paths = []
237 for source_path in source_paths:
238 enum_definitions = DoParseHeaderFile(source_path)
239 if not enum_definitions:
240 raise Exception('No enums found in %s\n'
241 'Did you forget prefixing enums with '
242 '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' %
243 source_path)
244 for enum_definition in enum_definitions:
245 package_path = enum_definition.enum_package.replace('.', os.path.sep)
246 file_name = enum_definition.class_name + '.java'
247 output_path = os.path.join(output_dir, package_path, file_name)
248 output_paths.append(output_path)
249 if not print_output_only:
250 build_utils.MakeDirectory(os.path.dirname(output_path))
251 DoWriteOutput(source_path, output_path, enum_definition)
252 return output_paths
255 def DoParseHeaderFile(path):
256 with open(path) as f:
257 return HeaderParser(f.readlines(), path).ParseDefinitions()
260 def GenerateOutput(source_path, enum_definition):
261 template = Template("""
262 // Copyright 2014 The Chromium Authors. All rights reserved.
263 // Use of this source code is governed by a BSD-style license that can be
264 // found in the LICENSE file.
266 // This file is autogenerated by
267 // ${SCRIPT_NAME}
268 // From
269 // ${SOURCE_PATH}
271 package ${PACKAGE};
273 public class ${CLASS_NAME} {
274 ${ENUM_ENTRIES}
276 """)
278 enum_template = Template(' public static final int ${NAME} = ${VALUE};')
279 enum_entries_string = []
280 for enum_name, enum_value in enum_definition.entries.iteritems():
281 values = {
282 'NAME': enum_name,
283 'VALUE': enum_value,
285 enum_entries_string.append(enum_template.substitute(values))
286 enum_entries_string = '\n'.join(enum_entries_string)
288 values = {
289 'CLASS_NAME': enum_definition.class_name,
290 'ENUM_ENTRIES': enum_entries_string,
291 'PACKAGE': enum_definition.enum_package,
292 'SCRIPT_NAME': GetScriptName(),
293 'SOURCE_PATH': source_path,
295 return template.substitute(values)
298 def DoWriteOutput(source_path, output_path, enum_definition):
299 with open(output_path, 'w') as out_file:
300 out_file.write(GenerateOutput(source_path, enum_definition))
302 def AssertFilesList(output_paths, assert_files_list):
303 actual = set(output_paths)
304 expected = set(assert_files_list)
305 if not actual == expected:
306 need_to_add = list(actual - expected)
307 need_to_remove = list(expected - actual)
308 raise Exception('Output files list does not match expectations. Please '
309 'add %s and remove %s.' % (need_to_add, need_to_remove))
311 def DoMain(argv):
312 usage = 'usage: %prog [options] output_dir input_file(s)...'
313 parser = optparse.OptionParser(usage=usage)
315 parser.add_option('--assert_file', action="append", default=[],
316 dest="assert_files_list", help='Assert that the given '
317 'file is an output. There can be multiple occurrences of '
318 'this flag.')
319 parser.add_option('--print_output_only', help='Only print output paths.',
320 action='store_true')
321 parser.add_option('--verbose', help='Print more information.',
322 action='store_true')
324 options, args = parser.parse_args(argv)
325 if len(args) < 2:
326 parser.error('Need to specify output directory and at least one input file')
327 output_paths = DoGenerate(args[0], args[1:],
328 print_output_only=options.print_output_only)
330 if options.assert_files_list:
331 AssertFilesList(output_paths, options.assert_files_list)
333 if options.verbose:
334 print 'Output paths:'
335 print '\n'.join(output_paths)
337 return ' '.join(output_paths)
339 if __name__ == '__main__':
340 DoMain(sys.argv[1:])