Add script to parse VehicleProperty.aidl to CSV.
Allow parsing the VHAL property definitions/annotations in
VehicleProperty.aidl into a CSV file so that we can generate
documentation.
Test: python generate_annotation_enums.py --output_csv /tmp/prop.csv
Bug: 288331747
Change-Id: Icc5d023c8efdc01ead220313d6a7c66d22c63a0a
diff --git a/automotive/vehicle/tools/generate_annotation_enums.py b/automotive/vehicle/tools/generate_annotation_enums.py
index 7276fe6..c432e9d 100755
--- a/automotive/vehicle/tools/generate_annotation_enums.py
+++ b/automotive/vehicle/tools/generate_annotation_enums.py
@@ -49,6 +49,8 @@
RE_COMMENT_END = re.compile('\s*\*\/')
RE_CHANGE_MODE = re.compile('\s*\* @change_mode (\S+)\s*')
RE_ACCESS = re.compile('\s*\* @access (\S+)\s*')
+RE_DATA_ENUM = re.compile('\s*\* @data_enum (\S+)\s*')
+RE_UNIT = re.compile('\s*\* @unit (\S+)\s+')
RE_VALUE = re.compile('\s*(\w+)\s*=(.*)')
LICENSE = """/*
@@ -166,55 +168,121 @@
"""
-class Converter:
+class PropertyConfig:
+ """Represents one VHAL property definition in VehicleProperty.aidl."""
- def __init__(self, name, annotation_re):
- self.name = name
- self.annotation_re = annotation_re
+ def __init__(self):
+ self.name = None
+ self.description = None
+ self.change_mode = None
+ self.access_modes = []
+ self.enum_types = []
+ self.unit_type = None
- def convert(self, input, output, header, footer, cpp):
+ def __repr__(self):
+ return self.__str__()
+
+ def __str__(self):
+ return ('PropertyConfig{{' +
+ 'name: {}, description: {}, change_mode: {}, access_modes: {}, enum_types: {}' +
+ ', unit_type: {}}}').format(self.name, self.description, self.change_mode,
+ self.access_modes, self.enum_types, self.unit_type)
+
+
+class FileParser:
+
+ def __init__(self):
+ self.configs = None
+
+ def parseFile(self, input_file):
+ """Parses the input VehicleProperty.aidl file into a list of property configs."""
processing = False
in_comment = False
- content = LICENSE + header
- annotation = None
- id = 0
- with open(input, 'r') as f:
+ configs = []
+ config = None
+ with open(input_file, 'r') as f:
for line in f.readlines():
if RE_ENUM_START.match(line):
processing = True
- annotation = None
elif RE_ENUM_END.match(line):
processing = False
if not processing:
continue
if RE_COMMENT_BEGIN.match(line):
in_comment = True
- annotation = None
+ config = PropertyConfig()
+ description = ''
if RE_COMMENT_END.match(line):
in_comment = False
if in_comment:
- match = self.annotation_re.match(line)
- if match and not annotation:
- annotation = match.group(1)
+ if not config.description:
+ sline = line.strip()
+ # Skip the first line of comment
+ if sline.startswith('*'):
+ # Remove the '*'.
+ sline = sline[1:].strip()
+ # We reach an empty line of comment, the description part is ending.
+ if sline == '':
+ config.description = description
+ else:
+ if description != '':
+ description += ' '
+ description += sline
+ match = RE_CHANGE_MODE.match(line)
+ if match:
+ config.change_mode = match.group(1).replace('VehiclePropertyChangeMode.', '')
+ match = RE_ACCESS.match(line)
+ if match:
+ config.access_modes.append(match.group(1).replace('VehiclePropertyAccess.', ''))
+ match = RE_UNIT.match(line)
+ if match:
+ config.unit_type = match.group(1)
+ match = RE_DATA_ENUM.match(line)
+ if match:
+ config.enum_types.append(match.group(1))
else:
match = RE_VALUE.match(line)
if match:
prop_name = match.group(1)
if prop_name == 'INVALID':
continue
- if not annotation:
+ if not config.change_mode:
raise Exception(
- 'No @' + self.name + ' annotation for property: ' + prop_name)
- if id != 0:
- content += '\n'
- if cpp:
- annotation = annotation.replace('.', '::')
- content += (TAB + TAB + '{VehicleProperty::' + prop_name + ', ' +
- annotation + '},')
- else:
- content += (TAB + TAB + 'Map.entry(VehicleProperty.' + prop_name + ', ' +
- annotation + '),')
- id += 1
+ 'No change_mode annotation for property: ' + prop_name)
+ if not config.access_modes:
+ raise Exception(
+ 'No access_mode annotation for property: ' + prop_name)
+ config.name = prop_name
+ configs.append(config)
+
+ self.configs = configs
+
+ def convert(self, output, header, footer, cpp, field):
+ """Converts the property config file to C++/Java output file."""
+ counter = 0
+ content = LICENSE + header
+ for config in self.configs:
+ if field == 'change_mode':
+ if cpp:
+ annotation = "VehiclePropertyChangeMode::" + config.change_mode
+ else:
+ annotation = "VehiclePropertyChangeMode." + config.change_mode
+ elif field == 'access_mode':
+ if cpp:
+ annotation = "VehiclePropertyAccess::" + config.access_modes[0]
+ else:
+ annotation = "VehiclePropertyAccess." + config.access_modes[0]
+ else:
+ raise Exception('Unknown field: ' + field)
+ if counter != 0:
+ content += '\n'
+ if cpp:
+ content += (TAB + TAB + '{VehicleProperty::' + config.name + ', ' +
+ annotation + '},')
+ else:
+ content += (TAB + TAB + 'Map.entry(VehicleProperty.' + config.name + ', ' +
+ annotation + '),')
+ counter += 1
# Remove the additional ',' at the end for the Java file.
if not cpp:
@@ -225,6 +293,30 @@
with open(output, 'w') as f:
f.write(content)
+ def outputAsCsv(self, output):
+ content = 'name,description,change mode,access mode,enum type,unit type\n'
+ for config in self.configs:
+ enum_types = None
+ if not config.enum_types:
+ enum_types = '/'
+ else:
+ enum_types = '/'.join(config.enum_types)
+ unit_type = config.unit_type
+ if not unit_type:
+ unit_type = '/'
+ access_modes = ''
+ content += '"{}","{}","{}","{}","{}","{}"\n'.format(
+ config.name,
+ # Need to escape quote as double quote.
+ config.description.replace('"', '""'),
+ config.change_mode,
+ '/'.join(config.access_modes),
+ enum_types,
+ unit_type)
+
+ with open(output, 'w+') as f:
+ f.write(content)
+
def createTempFile():
f = tempfile.NamedTemporaryFile(delete=False);
@@ -239,6 +331,8 @@
parser.add_argument('--preupload_files', nargs='+', required=False, help='modified files')
parser.add_argument('--check_only', required=False, action='store_true',
help='only check whether the generated files need update')
+ parser.add_argument('--output_csv', required=False,
+ help='Path to the parsing result in CSV style, useful for doc generation')
args = parser.parse_args();
android_top = None
output_folder = None
@@ -258,6 +352,12 @@
'at the android root')
aidl_file = os.path.join(android_top, PROP_AIDL_FILE_PATH)
+ f = FileParser();
+ f.parseFile(aidl_file)
+
+ if args.output_csv:
+ f.outputAsCsv(args.output_csv)
+ return
change_mode_cpp_file = os.path.join(android_top, CHANGE_MODE_CPP_FILE_PATH);
access_cpp_file = os.path.join(android_top, ACCESS_CPP_FILE_PATH);
@@ -281,14 +381,12 @@
temp_files.append(access_java_output)
try:
- c = Converter('change_mode', RE_CHANGE_MODE);
- c.convert(aidl_file, change_mode_cpp_output, CHANGE_MODE_CPP_HEADER, CHANGE_MODE_CPP_FOOTER,
- True)
- c.convert(aidl_file, change_mode_java_output, CHANGE_MODE_JAVA_HEADER,
- CHANGE_MODE_JAVA_FOOTER, False)
- c = Converter('access', RE_ACCESS)
- c.convert(aidl_file, access_cpp_output, ACCESS_CPP_HEADER, ACCESS_CPP_FOOTER, True)
- c.convert(aidl_file, access_java_output, ACCESS_JAVA_HEADER, ACCESS_JAVA_FOOTER, False)
+ f.convert(change_mode_cpp_output, CHANGE_MODE_CPP_HEADER, CHANGE_MODE_CPP_FOOTER,
+ True, 'change_mode')
+ f.convert(change_mode_java_output, CHANGE_MODE_JAVA_HEADER,
+ CHANGE_MODE_JAVA_FOOTER, False, 'change_mode')
+ f.convert(access_cpp_output, ACCESS_CPP_HEADER, ACCESS_CPP_FOOTER, True, 'access_mode')
+ f.convert(access_java_output, ACCESS_JAVA_HEADER, ACCESS_JAVA_FOOTER, False, 'access_mode')
if not args.check_only:
return