OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # |
| 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. |
| 6 |
| 7 import collections |
| 8 import re |
| 9 import optparse |
| 10 import os |
| 11 from string import Template |
| 12 import sys |
| 13 |
| 14 from util import build_utils |
| 15 |
| 16 # List of C++ types that are compatible with the Java code generated by this |
| 17 # script. |
| 18 # |
| 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'] |
| 24 |
| 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 |
| 34 |
| 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 |
| 39 |
| 40 @property |
| 41 def class_name(self): |
| 42 return self.class_name_override or self.original_enum_name |
| 43 |
| 44 def Finalize(self): |
| 45 self._Validate() |
| 46 self._AssignEntryIndices() |
| 47 self._StripPrefix() |
| 48 |
| 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)) |
| 56 |
| 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] |
| 73 |
| 74 |
| 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 = '' |
| 83 |
| 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 |
| 92 |
| 93 self.entries = entries |
| 94 |
| 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' |
| 99 |
| 100 known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key] |
| 101 |
| 102 def __init__(self): |
| 103 self._directives = {} |
| 104 |
| 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 |
| 109 |
| 110 @property |
| 111 def empty(self): |
| 112 return len(self._directives) == 0 |
| 113 |
| 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) |
| 121 |
| 122 |
| 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]*)\)$') |
| 136 |
| 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*$') |
| 143 |
| 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 |
| 152 |
| 153 def _ApplyGeneratorDirectives(self): |
| 154 self._generator_directives.UpdateDefinition(self._current_definition) |
| 155 self._generator_directives = DirectiveSet() |
| 156 |
| 157 def ParseDefinitions(self): |
| 158 for line in self._lines: |
| 159 self._ParseLine(line) |
| 160 return self._enum_definitions |
| 161 |
| 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) |
| 169 |
| 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) |
| 187 |
| 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)) |
| 193 |
| 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) |
| 206 |
| 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)) |
| 212 |
| 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 |
| 228 |
| 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:]) |
| 233 |
| 234 |
| 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 |
| 253 |
| 254 |
| 255 def DoParseHeaderFile(path): |
| 256 with open(path) as f: |
| 257 return HeaderParser(f.readlines(), path).ParseDefinitions() |
| 258 |
| 259 |
| 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. |
| 265 |
| 266 // This file is autogenerated by |
| 267 // ${SCRIPT_NAME} |
| 268 // From |
| 269 // ${SOURCE_PATH} |
| 270 |
| 271 package ${PACKAGE}; |
| 272 |
| 273 public class ${CLASS_NAME} { |
| 274 ${ENUM_ENTRIES} |
| 275 } |
| 276 """) |
| 277 |
| 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, |
| 284 } |
| 285 enum_entries_string.append(enum_template.substitute(values)) |
| 286 enum_entries_string = '\n'.join(enum_entries_string) |
| 287 |
| 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, |
| 294 } |
| 295 return template.substitute(values) |
| 296 |
| 297 |
| 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)) |
| 301 |
| 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)) |
| 310 |
| 311 def DoMain(argv): |
| 312 usage = 'usage: %prog [options] output_dir input_file(s)...' |
| 313 parser = optparse.OptionParser(usage=usage) |
| 314 |
| 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') |
| 323 |
| 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) |
| 329 |
| 330 if options.assert_files_list: |
| 331 AssertFilesList(output_paths, options.assert_files_list) |
| 332 |
| 333 if options.verbose: |
| 334 print 'Output paths:' |
| 335 print '\n'.join(output_paths) |
| 336 |
| 337 return ' '.join(output_paths) |
| 338 |
| 339 if __name__ == '__main__': |
| 340 DoMain(sys.argv[1:]) |
OLD | NEW |