Index: build/android/gyp/java_cpp_enum.py |
diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enum.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..c2f1764b1be9ade87751aa7a8c8f4afa1f93abc6 |
--- /dev/null |
+++ b/build/android/gyp/java_cpp_enum.py |
@@ -0,0 +1,340 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import collections |
+import re |
+import optparse |
+import os |
+from string import Template |
+import sys |
+ |
+from util import build_utils |
+ |
+# List of C++ types that are compatible with the Java code generated by this |
+# script. |
+# |
+# This script can parse .idl files however, at present it ignores special |
+# rules such as [cpp_enum_prefix_override="ax_attr"]. |
+ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char', |
+ 'short', 'unsigned short', |
+ 'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t'] |
+ |
+class EnumDefinition(object): |
+ def __init__(self, original_enum_name=None, class_name_override=None, |
+ enum_package=None, entries=None, fixed_type=None): |
+ self.original_enum_name = original_enum_name |
+ self.class_name_override = class_name_override |
+ self.enum_package = enum_package |
+ self.entries = collections.OrderedDict(entries or []) |
+ self.prefix_to_strip = None |
+ self.fixed_type = fixed_type |
+ |
+ def AppendEntry(self, key, value): |
+ if key in self.entries: |
+ raise Exception('Multiple definitions of key %s found.' % key) |
+ self.entries[key] = value |
+ |
+ @property |
+ def class_name(self): |
+ return self.class_name_override or self.original_enum_name |
+ |
+ def Finalize(self): |
+ self._Validate() |
+ self._AssignEntryIndices() |
+ self._StripPrefix() |
+ |
+ def _Validate(self): |
+ assert self.class_name |
+ assert self.enum_package |
+ assert self.entries |
+ if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST: |
+ raise Exception('Fixed type %s for enum %s not whitelisted.' % |
+ (self.fixed_type, self.class_name)) |
+ |
+ def _AssignEntryIndices(self): |
+ # Enums, if given no value, are given the value of the previous enum + 1. |
+ if not all(self.entries.values()): |
+ prev_enum_value = -1 |
+ for key, value in self.entries.iteritems(): |
+ if not value: |
+ self.entries[key] = prev_enum_value + 1 |
+ elif value in self.entries: |
+ self.entries[key] = self.entries[value] |
+ else: |
+ try: |
+ self.entries[key] = int(value) |
+ except ValueError: |
+ raise Exception('Could not interpret integer from enum value "%s" ' |
+ 'for key %s.' % (value, key)) |
+ prev_enum_value = self.entries[key] |
+ |
+ |
+ def _StripPrefix(self): |
+ prefix_to_strip = self.prefix_to_strip |
+ if not prefix_to_strip: |
+ prefix_to_strip = self.original_enum_name |
+ prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper() |
+ prefix_to_strip += '_' |
+ if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]): |
+ prefix_to_strip = '' |
+ |
+ entries = collections.OrderedDict() |
+ for (k, v) in self.entries.iteritems(): |
+ stripped_key = k.replace(prefix_to_strip, '', 1) |
+ if isinstance(v, basestring): |
+ stripped_value = v.replace(prefix_to_strip, '', 1) |
+ else: |
+ stripped_value = v |
+ entries[stripped_key] = stripped_value |
+ |
+ self.entries = entries |
+ |
+class DirectiveSet(object): |
+ class_name_override_key = 'CLASS_NAME_OVERRIDE' |
+ enum_package_key = 'ENUM_PACKAGE' |
+ prefix_to_strip_key = 'PREFIX_TO_STRIP' |
+ |
+ known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key] |
+ |
+ def __init__(self): |
+ self._directives = {} |
+ |
+ def Update(self, key, value): |
+ if key not in DirectiveSet.known_keys: |
+ raise Exception("Unknown directive: " + key) |
+ self._directives[key] = value |
+ |
+ @property |
+ def empty(self): |
+ return len(self._directives) == 0 |
+ |
+ def UpdateDefinition(self, definition): |
+ definition.class_name_override = self._directives.get( |
+ DirectiveSet.class_name_override_key, '') |
+ definition.enum_package = self._directives.get( |
+ DirectiveSet.enum_package_key) |
+ definition.prefix_to_strip = self._directives.get( |
+ DirectiveSet.prefix_to_strip_key) |
+ |
+ |
+class HeaderParser(object): |
+ single_line_comment_re = re.compile(r'\s*//') |
+ multi_line_comment_start_re = re.compile(r'\s*/\*') |
+ enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?') |
+ enum_end_re = re.compile(r'^\s*}\s*;\.*$') |
+ generator_directive_re = re.compile( |
+ r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$') |
+ multi_line_generator_directive_start_re = re.compile( |
+ r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*\(([\.\w]*)$') |
+ multi_line_directive_continuation_re = re.compile( |
+ r'^\s*//\s+([\.\w]+)$') |
+ multi_line_directive_end_re = re.compile( |
+ r'^\s*//\s+([\.\w]*)\)$') |
+ |
+ optional_class_or_struct_re = r'(class|struct)?' |
+ enum_name_re = r'(\w+)' |
+ optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?' |
+ enum_start_re = re.compile(r'^\s*(?:\[cpp.*\])?\s*enum\s+' + |
+ optional_class_or_struct_re + '\s*' + enum_name_re + '\s*' + |
+ optional_fixed_type_re + '\s*{\s*$') |
+ |
+ def __init__(self, lines, path=None): |
+ self._lines = lines |
+ self._path = path |
+ self._enum_definitions = [] |
+ self._in_enum = False |
+ self._current_definition = None |
+ self._generator_directives = DirectiveSet() |
+ self._multi_line_generator_directive = None |
+ |
+ def _ApplyGeneratorDirectives(self): |
+ self._generator_directives.UpdateDefinition(self._current_definition) |
+ self._generator_directives = DirectiveSet() |
+ |
+ def ParseDefinitions(self): |
+ for line in self._lines: |
+ self._ParseLine(line) |
+ return self._enum_definitions |
+ |
+ def _ParseLine(self, line): |
+ if self._multi_line_generator_directive: |
+ self._ParseMultiLineDirectiveLine(line) |
+ elif not self._in_enum: |
+ self._ParseRegularLine(line) |
+ else: |
+ self._ParseEnumLine(line) |
+ |
+ def _ParseEnumLine(self, line): |
+ if HeaderParser.single_line_comment_re.match(line): |
+ return |
+ if HeaderParser.multi_line_comment_start_re.match(line): |
+ raise Exception('Multi-line comments in enums are not supported in ' + |
+ self._path) |
+ enum_end = HeaderParser.enum_end_re.match(line) |
+ enum_entry = HeaderParser.enum_line_re.match(line) |
+ if enum_end: |
+ self._ApplyGeneratorDirectives() |
+ self._current_definition.Finalize() |
+ self._enum_definitions.append(self._current_definition) |
+ self._in_enum = False |
+ elif enum_entry: |
+ enum_key = enum_entry.groups()[0] |
+ enum_value = enum_entry.groups()[2] |
+ self._current_definition.AppendEntry(enum_key, enum_value) |
+ |
+ def _ParseMultiLineDirectiveLine(self, line): |
+ multi_line_directive_continuation = ( |
+ HeaderParser.multi_line_directive_continuation_re.match(line)) |
+ multi_line_directive_end = ( |
+ HeaderParser.multi_line_directive_end_re.match(line)) |
+ |
+ if multi_line_directive_continuation: |
+ value_cont = multi_line_directive_continuation.groups()[0] |
+ self._multi_line_generator_directive[1].append(value_cont) |
+ elif multi_line_directive_end: |
+ directive_name = self._multi_line_generator_directive[0] |
+ directive_value = "".join(self._multi_line_generator_directive[1]) |
+ directive_value += multi_line_directive_end.groups()[0] |
+ self._multi_line_generator_directive = None |
+ self._generator_directives.Update(directive_name, directive_value) |
+ else: |
+ raise Exception('Malformed multi-line directive declaration in ' + |
+ self._path) |
+ |
+ def _ParseRegularLine(self, line): |
+ enum_start = HeaderParser.enum_start_re.match(line) |
+ generator_directive = HeaderParser.generator_directive_re.match(line) |
+ multi_line_generator_directive_start = ( |
+ HeaderParser.multi_line_generator_directive_start_re.match(line)) |
+ |
+ if generator_directive: |
+ directive_name = generator_directive.groups()[0] |
+ directive_value = generator_directive.groups()[1] |
+ self._generator_directives.Update(directive_name, directive_value) |
+ elif multi_line_generator_directive_start: |
+ directive_name = multi_line_generator_directive_start.groups()[0] |
+ directive_value = multi_line_generator_directive_start.groups()[1] |
+ self._multi_line_generator_directive = (directive_name, [directive_value]) |
+ elif enum_start: |
+ if self._generator_directives.empty: |
+ return |
+ self._current_definition = EnumDefinition( |
+ original_enum_name=enum_start.groups()[1], |
+ fixed_type=enum_start.groups()[3]) |
+ self._in_enum = True |
+ |
+def GetScriptName(): |
+ script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) |
+ build_index = script_components.index('build') |
+ return os.sep.join(script_components[build_index:]) |
+ |
+ |
+def DoGenerate(output_dir, source_paths, print_output_only=False): |
+ output_paths = [] |
+ for source_path in source_paths: |
+ enum_definitions = DoParseHeaderFile(source_path) |
+ if not enum_definitions: |
+ raise Exception('No enums found in %s\n' |
+ 'Did you forget prefixing enums with ' |
+ '"// GENERATED_JAVA_ENUM_PACKAGE: foo"?' % |
+ source_path) |
+ for enum_definition in enum_definitions: |
+ package_path = enum_definition.enum_package.replace('.', os.path.sep) |
+ file_name = enum_definition.class_name + '.java' |
+ output_path = os.path.join(output_dir, package_path, file_name) |
+ output_paths.append(output_path) |
+ if not print_output_only: |
+ build_utils.MakeDirectory(os.path.dirname(output_path)) |
+ DoWriteOutput(source_path, output_path, enum_definition) |
+ return output_paths |
+ |
+ |
+def DoParseHeaderFile(path): |
+ with open(path) as f: |
+ return HeaderParser(f.readlines(), path).ParseDefinitions() |
+ |
+ |
+def GenerateOutput(source_path, enum_definition): |
+ template = Template(""" |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// This file is autogenerated by |
+// ${SCRIPT_NAME} |
+// From |
+// ${SOURCE_PATH} |
+ |
+package ${PACKAGE}; |
+ |
+public class ${CLASS_NAME} { |
+${ENUM_ENTRIES} |
+} |
+""") |
+ |
+ enum_template = Template(' public static final int ${NAME} = ${VALUE};') |
+ enum_entries_string = [] |
+ for enum_name, enum_value in enum_definition.entries.iteritems(): |
+ values = { |
+ 'NAME': enum_name, |
+ 'VALUE': enum_value, |
+ } |
+ enum_entries_string.append(enum_template.substitute(values)) |
+ enum_entries_string = '\n'.join(enum_entries_string) |
+ |
+ values = { |
+ 'CLASS_NAME': enum_definition.class_name, |
+ 'ENUM_ENTRIES': enum_entries_string, |
+ 'PACKAGE': enum_definition.enum_package, |
+ 'SCRIPT_NAME': GetScriptName(), |
+ 'SOURCE_PATH': source_path, |
+ } |
+ return template.substitute(values) |
+ |
+ |
+def DoWriteOutput(source_path, output_path, enum_definition): |
+ with open(output_path, 'w') as out_file: |
+ out_file.write(GenerateOutput(source_path, enum_definition)) |
+ |
+def AssertFilesList(output_paths, assert_files_list): |
+ actual = set(output_paths) |
+ expected = set(assert_files_list) |
+ if not actual == expected: |
+ need_to_add = list(actual - expected) |
+ need_to_remove = list(expected - actual) |
+ raise Exception('Output files list does not match expectations. Please ' |
+ 'add %s and remove %s.' % (need_to_add, need_to_remove)) |
+ |
+def DoMain(argv): |
+ usage = 'usage: %prog [options] output_dir input_file(s)...' |
+ parser = optparse.OptionParser(usage=usage) |
+ |
+ parser.add_option('--assert_file', action="append", default=[], |
+ dest="assert_files_list", help='Assert that the given ' |
+ 'file is an output. There can be multiple occurrences of ' |
+ 'this flag.') |
+ parser.add_option('--print_output_only', help='Only print output paths.', |
+ action='store_true') |
+ parser.add_option('--verbose', help='Print more information.', |
+ action='store_true') |
+ |
+ options, args = parser.parse_args(argv) |
+ if len(args) < 2: |
+ parser.error('Need to specify output directory and at least one input file') |
+ output_paths = DoGenerate(args[0], args[1:], |
+ print_output_only=options.print_output_only) |
+ |
+ if options.assert_files_list: |
+ AssertFilesList(output_paths, options.assert_files_list) |
+ |
+ if options.verbose: |
+ print 'Output paths:' |
+ print '\n'.join(output_paths) |
+ |
+ return ' '.join(output_paths) |
+ |
+if __name__ == '__main__': |
+ DoMain(sys.argv[1:]) |