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

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

Issue 484603004: New C++ -> Java enum build rule + parser/generator. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed aosp bot and added gn rule Created 6 years, 3 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 | Annotate | Revision Log
OLDNEW
(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 class EnumDefinition(object):
17 def __init__(self, class_name=None, class_package=None, entries=None):
18 self.class_name = class_name
19 self.class_package = class_package
20 self.entries = collections.OrderedDict(entries or [])
21 self.prefix_to_strip = ''
22
23 def AppendEntry(self, key, value):
24 if key in self.entries:
25 raise Exception('Multiple definitions of key %s found.' % key)
26 self.entries[key] = value
27
28 def Finalize(self):
29 self._Validate()
30 self._AssignEntryIndices()
31 self._StripPrefix()
32
33 def _Validate(self):
34 assert self.class_name
35 assert self.class_package
36 assert self.entries
37
38 def _AssignEntryIndices(self):
39 # Supporting the same set enum value assignments the compiler does is rather
40 # complicated, so we limit ourselves to these cases:
41 # - all the enum constants have values assigned,
42 # - enum constants reference other enum constants or have no value assigned.
43
44 if not all(self.entries.values()):
45 index = 0
46 for key, value in self.entries.iteritems():
47 if not value:
48 self.entries[key] = index
49 index = index + 1
50 elif value in self.entries:
51 self.entries[key] = self.entries[value]
52 else:
53 raise Exception('You can only reference other enum constants unless '
54 'you assign values to all of the constants.')
55
56 def _StripPrefix(self):
57 if not self.prefix_to_strip:
58 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', self.class_name).upper()
59 prefix_to_strip += '_'
60 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
61 prefix_to_strip = ''
62 else:
63 prefix_to_strip = self.prefix_to_strip
64 entries = ((k.replace(prefix_to_strip, '', 1), v) for (k, v) in
65 self.entries.iteritems())
66 self.entries = collections.OrderedDict(entries)
67
68 class HeaderParser(object):
69 single_line_comment_re = re.compile(r'\s*//')
70 multi_line_comment_start_re = re.compile(r'\s*/\*')
71 enum_start_re = re.compile(r'^\s*enum\s+(\w+)\s+{\s*$')
72 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?\s*$')
73 enum_end_re = re.compile(r'^\s*}\s*;\s*$')
74 generator_directive_re = re.compile(
75 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
76
77 def __init__(self, lines):
78 self._lines = lines
79 self._enum_definitions = []
80 self._in_enum = False
81 self._current_definition = None
82 self._generator_directives = {}
83
84 def ParseDefinitions(self):
85 for line in self._lines:
86 self._ParseLine(line)
87 return self._enum_definitions
88
89 def _ParseLine(self, line):
90 if not self._in_enum:
91 self._ParseRegularLine(line)
92 else:
93 self._ParseEnumLine(line)
94
95 def _ParseEnumLine(self, line):
96 if HeaderParser.single_line_comment_re.match(line):
97 return
98 if HeaderParser.multi_line_comment_start_re.match(line):
99 raise Exception('Multi-line comments in enums are not supported.')
100 enum_end = HeaderParser.enum_end_re.match(line)
101 enum_entry = HeaderParser.enum_line_re.match(line)
102 if enum_end:
103 self._ApplyGeneratorDirectives()
104 self._current_definition.Finalize()
105 self._enum_definitions.append(self._current_definition)
106 self._in_enum = False
107 elif enum_entry:
108 enum_key = enum_entry.groups()[0]
109 enum_value = enum_entry.groups()[2]
110 self._current_definition.AppendEntry(enum_key, enum_value)
111
112 def _GetCurrentEnumPackageName(self):
113 return self._generator_directives.get('ENUM_PACKAGE')
114
115 def _GetCurrentEnumPrefixToStrip(self):
116 return self._generator_directives.get('PREFIX_TO_STRIP', '')
117
118 def _ApplyGeneratorDirectives(self):
119 current_definition = self._current_definition
120 current_definition.class_package = self._GetCurrentEnumPackageName()
121 current_definition.prefix_to_strip = self._GetCurrentEnumPrefixToStrip()
122 self._generator_directives = {}
123
124 def _ParseRegularLine(self, line):
125 enum_start = HeaderParser.enum_start_re.match(line)
126 generator_directive = HeaderParser.generator_directive_re.match(line)
127 if enum_start:
128 if not self._GetCurrentEnumPackageName():
129 return
130 self._current_definition = EnumDefinition()
131 self._current_definition.class_name = enum_start.groups()[0]
132 self._in_enum = True
133 elif generator_directive:
134 directive_name = generator_directive.groups()[0]
135 directive_value = generator_directive.groups()[1]
136 self._generator_directives[directive_name] = directive_value
137
138
139 def GetScriptName():
140 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
141 build_index = script_components.index('build')
142 return os.sep.join(script_components[build_index:])
143
144
145 def DoGenerate(options, path):
146 output_paths = []
147 enum_definitions = DoParseHeaderFile(path)
148 for enum_definition in enum_definitions:
149 package_path = enum_definition.class_package.replace('.', os.path.sep)
150 file_name = enum_definition.class_name + '.java'
151 output_path = os.path.join(options.output_dir, package_path, file_name)
152 output_paths.append(output_path)
153 if not options.print_output_only:
154 build_utils.MakeDirectory(os.path.dirname(output_path))
155 DoWriteOutput(path, output_path, enum_definition)
156 return output_paths
157
158
159 def DoParseHeaderFile(path):
160 with open(path) as f:
161 return HeaderParser(f.readlines()).ParseDefinitions()
162
163
164 def GenerateOutput(source_path, enum_definition):
165 template = Template("""
166 // Copyright 2014 The Chromium Authors. All rights reserved.
167 // Use of this source code is governed by a BSD-style license that can be
168 // found in the LICENSE file.
169
170 // This file is autogenerated by
171 // ${SCRIPT_NAME}
172 // From
173 // ${SOURCE_PATH}
174
175 package ${PACKAGE};
176
177 public class ${CLASS_NAME} {
178 ${ENUM_ENTRIES}
179 }
180 """)
181
182 enum_template = Template(' public static final int ${NAME} = ${VALUE};')
183 enum_entries_string = []
184 for enum_name, enum_value in enum_definition.entries.iteritems():
185 values = {
186 'NAME': enum_name,
187 'VALUE': enum_value,
188 }
189 enum_entries_string.append(enum_template.substitute(values))
190 enum_entries_string = '\n'.join(enum_entries_string)
191
192 values = {
193 'CLASS_NAME': enum_definition.class_name,
194 'ENUM_ENTRIES': enum_entries_string,
195 'PACKAGE': enum_definition.class_package,
196 'SCRIPT_NAME': GetScriptName(),
197 'SOURCE_PATH': source_path,
198 }
199 return template.substitute(values)
200
201
202 def DoWriteOutput(source_path, output_path, enum_definition):
203 with open(output_path, 'w') as out_file:
204 out_file.write(GenerateOutput(source_path, enum_definition))
205
206 def AssertFilesList(output_paths, assert_files_list):
207 actual = set(output_paths)
208 expected = set(assert_files_list)
209 if not actual == expected:
210 need_to_add = list(actual - expected)
211 need_to_remove = list(expected - actual)
212 raise Exception('Output files list does not match expectations. Please '
213 'add %s and remove %s.' % (need_to_add, need_to_remove))
214
215 def DoMain(argv):
216 parser = optparse.OptionParser()
217
218 parser.add_option('--assert_file', action="append", default=[],
219 dest="assert_files_list", help='Assert that the given '
220 'file is an output. There can be multiple occurrences of '
221 'this flag.')
222 parser.add_option('--output_dir', help='Base path for generated files.')
223 parser.add_option('--print_output_only', help='Only print output paths.',
224 action='store_true')
225
226 options, args = parser.parse_args(argv)
227
228 output_paths = DoGenerate(options, args[0])
229
230 if options.assert_files_list:
231 AssertFilesList(output_paths, options.assert_files_list)
232
233 return " ".join(output_paths)
234
235 if __name__ == '__main__':
236 DoMain(sys.argv[1:])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698