Home | History | Annotate | Download | only in gyp
      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, source_paths):
    146   output_paths = []
    147   for source_path in source_paths:
    148     enum_definitions = DoParseHeaderFile(source_path)
    149     for enum_definition in enum_definitions:
    150       package_path = enum_definition.class_package.replace('.', os.path.sep)
    151       file_name = enum_definition.class_name + '.java'
    152       output_path = os.path.join(options.output_dir, package_path, file_name)
    153       output_paths.append(output_path)
    154       if not options.print_output_only:
    155         build_utils.MakeDirectory(os.path.dirname(output_path))
    156         DoWriteOutput(source_path, output_path, enum_definition)
    157   return output_paths
    158 
    159 
    160 def DoParseHeaderFile(path):
    161   with open(path) as f:
    162     return HeaderParser(f.readlines()).ParseDefinitions()
    163 
    164 
    165 def GenerateOutput(source_path, enum_definition):
    166   template = Template("""
    167 // Copyright 2014 The Chromium Authors. All rights reserved.
    168 // Use of this source code is governed by a BSD-style license that can be
    169 // found in the LICENSE file.
    170 
    171 // This file is autogenerated by
    172 //     ${SCRIPT_NAME}
    173 // From
    174 //     ${SOURCE_PATH}
    175 
    176 package ${PACKAGE};
    177 
    178 public class ${CLASS_NAME} {
    179 ${ENUM_ENTRIES}
    180 }
    181 """)
    182 
    183   enum_template = Template('  public static final int ${NAME} = ${VALUE};')
    184   enum_entries_string = []
    185   for enum_name, enum_value in enum_definition.entries.iteritems():
    186     values = {
    187         'NAME': enum_name,
    188         'VALUE': enum_value,
    189     }
    190     enum_entries_string.append(enum_template.substitute(values))
    191   enum_entries_string = '\n'.join(enum_entries_string)
    192 
    193   values = {
    194       'CLASS_NAME': enum_definition.class_name,
    195       'ENUM_ENTRIES': enum_entries_string,
    196       'PACKAGE': enum_definition.class_package,
    197       'SCRIPT_NAME': GetScriptName(),
    198       'SOURCE_PATH': source_path,
    199   }
    200   return template.substitute(values)
    201 
    202 
    203 def DoWriteOutput(source_path, output_path, enum_definition):
    204   with open(output_path, 'w') as out_file:
    205     out_file.write(GenerateOutput(source_path, enum_definition))
    206 
    207 def AssertFilesList(output_paths, assert_files_list):
    208   actual = set(output_paths)
    209   expected = set(assert_files_list)
    210   if not actual == expected:
    211     need_to_add = list(actual - expected)
    212     need_to_remove = list(expected - actual)
    213     raise Exception('Output files list does not match expectations. Please '
    214                     'add %s and remove %s.' % (need_to_add, need_to_remove))
    215 
    216 def DoMain(argv):
    217   parser = optparse.OptionParser()
    218 
    219   parser.add_option('--assert_file', action="append", default=[],
    220                     dest="assert_files_list", help='Assert that the given '
    221                     'file is an output. There can be multiple occurrences of '
    222                     'this flag.')
    223   parser.add_option('--output_dir', help='Base path for generated files.')
    224   parser.add_option('--print_output_only', help='Only print output paths.',
    225                     action='store_true')
    226 
    227   options, args = parser.parse_args(argv)
    228 
    229   output_paths = DoGenerate(options, args)
    230 
    231   if options.assert_files_list:
    232     AssertFilesList(output_paths, options.assert_files_list)
    233 
    234   return " ".join(output_paths)
    235 
    236 if __name__ == '__main__':
    237   DoMain(sys.argv[1:])
    238