blob: 4ed19d324d983c83ee1e35bfb8698474e1b253d3 [file] [log] [blame]
#!/usr/bin/python3
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A script to generate Java files and CPP header files based on annotations in VehicleProperty.aidl
Need ANDROID_BUILD_TOP environmental variable to be set. This script will update
ChangeModeForVehicleProperty.h and AccessForVehicleProperty.h under generated_lib/version/cpp and
ChangeModeForVehicleProperty.java, AccessForVehicleProperty.java, EnumForVehicleProperty.java
UnitsForVehicleProperty.java under generated_lib/version/java.
Usage:
$ python generate_annotation_enums.py
"""
import argparse
import filecmp
import os
import re
import sys
import tempfile
# Keep this updated with the latest in-development property version.
PROPERTY_VERSION = '4'
PROP_AIDL_FILE_PATH = ('hardware/interfaces/automotive/vehicle/aidl_property/android/hardware/' +
'automotive/vehicle/VehicleProperty.aidl')
GENERATED_LIB = ('hardware/interfaces/automotive/vehicle/aidl/generated_lib/' + PROPERTY_VERSION +
'/')
CHANGE_MODE_CPP_FILE_PATH = GENERATED_LIB + '/cpp/ChangeModeForVehicleProperty.h'
ACCESS_CPP_FILE_PATH = GENERATED_LIB + '/cpp/AccessForVehicleProperty.h'
CHANGE_MODE_JAVA_FILE_PATH = GENERATED_LIB + '/java/ChangeModeForVehicleProperty.java'
ACCESS_JAVA_FILE_PATH = GENERATED_LIB + '/java/AccessForVehicleProperty.java'
ENUM_JAVA_FILE_PATH = GENERATED_LIB + '/java/EnumForVehicleProperty.java'
UNITS_JAVA_FILE_PATH = GENERATED_LIB + '/java/UnitsForVehicleProperty.java'
VERSION_CPP_FILE_PATH = GENERATED_LIB + '/cpp/VersionForVehicleProperty.h'
ANNOTATIONS_CPP_FILE_PATH = GENERATED_LIB + '/cpp/AnnotationsForVehicleProperty.h'
ANNOTATIONS_JAVA_FILE_PATH = GENERATED_LIB + '/java/AnnotationsForVehicleProperty.java'
SCRIPT_PATH = 'hardware/interfaces/automotive/vehicle/tools/generate_annotation_enums.py'
TAB = ' '
RE_ENUM_START = re.compile('\s*enum VehicleProperty \{')
RE_ENUM_END = re.compile('\s*\}\;')
RE_COMMENT_BEGIN = re.compile('\s*\/\*\*?')
RE_COMMENT_END = re.compile('\s*\*\/')
RE_CHANGE_MODE = re.compile('\s*\* @change_mode (\S+)\s*')
RE_VERSION = re.compile('\s*\* @version (\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*=(.*)')
RE_ANNOTATION = re.compile('\s*\* @(\S+)\s*')
SUPPORTED_ANNOTATIONS = ['change_mode', 'access', 'unit', 'data_enum', 'data_enum_bit_flags',
'version', 'require_min_max_supported_value', 'require_supported_values_list',
'legacy_supported_values_in_config']
# Non static data_enum properties that do not require supported values list.
# These properties are either deprecated or for internal use only.
ENUM_PROPERTIES_WITHOUT_SUPPORTED_VALUES = [
# deprecated
'TURN_SIGNAL_STATE',
# The supported values are exposed through HVAC_FAN_DIRECTION_AVAILABLE
'HVAC_FAN_DIRECTION',
# Internal use only
'HW_ROTARY_INPUT',
# Internal use only
'HW_CUSTOM_INPUT',
# Internal use only
'SHUTDOWN_REQUEST',
# Internal use only
'CAMERA_SERVICE_CURRENT_STATE'
]
LICENSE = """/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* DO NOT EDIT MANUALLY!!!
*
* Generated by tools/generate_annotation_enums.py.
*/
// clang-format off
"""
CHANGE_MODE_CPP_HEADER = """#pragma once
#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h>
#include <aidl/android/hardware/automotive/vehicle/VehiclePropertyChangeMode.h>
#include <unordered_map>
namespace aidl {
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
std::unordered_map<VehicleProperty, VehiclePropertyChangeMode> ChangeModeForVehicleProperty = {
"""
CPP_FOOTER = """
};
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android
} // aidl
"""
ACCESS_CPP_HEADER = """#pragma once
#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h>
#include <aidl/android/hardware/automotive/vehicle/VehiclePropertyAccess.h>
#include <unordered_map>
namespace aidl {
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
std::unordered_map<VehicleProperty, VehiclePropertyAccess> AccessForVehicleProperty = {
"""
VERSION_CPP_HEADER = """#pragma once
#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h>
#include <unordered_map>
namespace aidl {
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
std::unordered_map<VehicleProperty, int32_t> VersionForVehicleProperty = {
"""
ANNOTATIONS_CPP_HEADER = """#pragma once
#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace aidl {
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
std::unordered_map<VehicleProperty, std::unordered_set<std::string>>
AnnotationsForVehicleProperty = {
"""
CHANGE_MODE_JAVA_HEADER = """package android.hardware.automotive.vehicle;
import java.util.Map;
public final class ChangeModeForVehicleProperty {
public static final Map<Integer, Integer> values = Map.ofEntries(
"""
JAVA_FOOTER = """
);
}
"""
ACCESS_JAVA_HEADER = """package android.hardware.automotive.vehicle;
import java.util.Map;
public final class AccessForVehicleProperty {
public static final Map<Integer, Integer> values = Map.ofEntries(
"""
ENUM_JAVA_HEADER = """package android.hardware.automotive.vehicle;
import java.util.List;
import java.util.Map;
public final class EnumForVehicleProperty {
public static final Map<Integer, List<Class<?>>> values = Map.ofEntries(
"""
UNITS_JAVA_HEADER = """package android.hardware.automotive.vehicle;
import java.util.Map;
public final class UnitsForVehicleProperty {
public static final Map<Integer, Integer> values = Map.ofEntries(
"""
ANNOTATIONS_JAVA_HEADER = """package android.hardware.automotive.vehicle;
import java.util.Set;
import java.util.Map;
public final class AnnotationsForVehicleProperty {
public static final Map<Integer, Set<String>> values = Map.ofEntries(
"""
class PropertyConfig:
"""Represents one VHAL property definition in VehicleProperty.aidl."""
def __init__(self):
self.name = None
self.description = None
self.comment = None
self.change_mode = None
self.access_modes = []
self.enum_types = []
self.unit_type = None
self.version = None
# Use a set to avoid duplicate annotation.
self.annotations = set()
def __repr__(self):
return self.__str__()
def __str__(self):
return ('PropertyConfig{{' +
'name: {}, description: {}, change_mode: {}, access_modes: {}, enum_types: {}' +
', unit_type: {}, version: {}, comment: {}}}').format(self.name, self.description,
self.change_mode, self.access_modes, self.enum_types, self.unit_type,
self.version, self.comment)
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
configs = []
config = None
with open(input_file, 'r') as f:
for line in f.readlines():
if RE_ENUM_START.match(line):
processing = True
elif RE_ENUM_END.match(line):
processing = False
if not processing:
continue
if RE_COMMENT_BEGIN.match(line):
in_comment = True
config = PropertyConfig()
# Use an array so that we could modify the string in parseComment.
description = ['']
continue
if RE_COMMENT_END.match(line):
in_comment = False
if in_comment:
# We will update the string in description in this function.
self.parseComment(line, config, description)
else:
match = RE_VALUE.match(line)
if match:
prop_name = match.group(1)
if prop_name == 'INVALID':
continue
if not config.change_mode:
raise Exception(
'No change_mode annotation for property: ' + prop_name)
if not config.access_modes:
raise Exception(
'No access_mode annotation for property: ' + prop_name)
if not config.version:
raise Exception(
'No version annotation for property: ' + prop_name)
if ('data_enum' in config.annotations and
'require_supported_values_list' not in config.annotations and
config.change_mode != 'STATIC' and
prop_name not in ENUM_PROPERTIES_WITHOUT_SUPPORTED_VALUES):
raise Exception(
'The property: ' + prop_name + ' has @data_enum '
'annotation but does not have @require_supported_values_list'
', either add the annotation or add the property name to '
'ENUM_PROPERTIES_WITHOUT_SUPPORTED_VALUES in '
'generate_annotation_enums.py')
config.name = prop_name
configs.append(config)
self.configs = configs
def parseComment(self, line, config, description):
match_annotation = RE_ANNOTATION.match(line)
if match_annotation:
annotation = match_annotation.group(1)
if annotation not in SUPPORTED_ANNOTATIONS:
raise Exception('Annotation: @' + annotation + " is not supported, typo?")
config.annotations.add(annotation)
match = RE_CHANGE_MODE.match(line)
if match:
config.change_mode = match.group(1).replace('VehiclePropertyChangeMode.', '')
return
match = RE_ACCESS.match(line)
if match:
config.access_modes.append(match.group(1).replace('VehiclePropertyAccess.', ''))
return
match = RE_UNIT.match(line)
if match:
config.unit_type = match.group(1)
return
match = RE_DATA_ENUM.match(line)
if match:
config.enum_types.append(match.group(1))
return
match = RE_VERSION.match(line)
if match:
if config.version != None:
raise Exception('Duplicate version annotation for property: ' + prop_name)
config.version = match.group(1)
return
sline = line.strip()
if sline.startswith('*'):
# Remove the '*'.
sline = sline[1:].strip()
if not config.description:
# We reach an empty line of comment, the description part is ending.
if sline == '':
config.description = description[0]
else:
if description[0] != '':
description[0] += ' '
description[0] += sline
else:
if not config.comment:
if sline != '':
# This is the first line for comment.
config.comment = sline
else:
if sline != '':
# Concat this line with the previous line's comment with a space.
config.comment += ' ' + sline
else:
# Treat empty line comment as a new line.
config.comment += '\n'
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:
value = "VehiclePropertyChangeMode::" + config.change_mode
else:
value = "VehiclePropertyChangeMode." + config.change_mode
elif field == 'access_mode':
if cpp:
value = "VehiclePropertyAccess::" + config.access_modes[0]
else:
value = "VehiclePropertyAccess." + config.access_modes[0]
elif field == 'enum_types':
if len(config.enum_types) < 1:
continue
if not cpp:
value = "List.of(" + ', '.join([class_name + ".class" for class_name in config.enum_types]) + ")"
elif field == 'unit_type':
if not config.unit_type:
continue
if not cpp:
value = config.unit_type
elif field == 'version':
if cpp:
value = config.version
elif field == 'annotations':
if len(config.annotations) < 1:
continue
joined_annotation_strings = ', '.join(['"' + annotation + '"' for annotation in config.annotations])
if cpp:
value = "{" + joined_annotation_strings + "}"
else:
value = "Set.of(" + joined_annotation_strings + ")"
else:
raise Exception('Unknown field: ' + field)
if counter != 0:
content += '\n'
if cpp:
content += (TAB + TAB + '{VehicleProperty::' + config.name + ', ' +
value + '},')
else:
content += (TAB + TAB + 'Map.entry(VehicleProperty.' + config.name + ', ' +
value + '),')
counter += 1
# Remove the additional ',' at the end for the Java file.
if not cpp:
content = content[:-1]
content += footer
with open(output, 'w') as f:
f.write(content)
def outputAsCsv(self, output):
content = 'name,description,change mode,access mode,enum type,unit type,comment\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 = ''
comment = config.comment
if not comment:
comment = ''
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,
comment.replace('"', '""'))
with open(output, 'w+') as f:
f.write(content)
def createTempFile():
f = tempfile.NamedTemporaryFile(delete=False);
f.close();
return f.name
class GeneratedFile:
def __init__(self, type):
self.type = type
self.cpp_file_path = None
self.java_file_path = None
self.cpp_header = None
self.java_header = None
self.cpp_footer = None
self.java_footer = None
self.cpp_output_file = None
self.java_output_file = None
def setCppFilePath(self, cpp_file_path):
self.cpp_file_path = cpp_file_path
def setJavaFilePath(self, java_file_path):
self.java_file_path = java_file_path
def setCppHeader(self, cpp_header):
self.cpp_header = cpp_header
def setCppFooter(self, cpp_footer):
self.cpp_footer = cpp_footer
def setJavaHeader(self, java_header):
self.java_header = java_header
def setJavaFooter(self, java_footer):
self.java_footer = java_footer
def convert(self, file_parser, check_only, temp_files):
if self.cpp_file_path:
output_file = GeneratedFile._getOutputFile(self.cpp_file_path, check_only, temp_files)
file_parser.convert(output_file, self.cpp_header, self.cpp_footer, True, self.type)
self.cpp_output_file = output_file
if self.java_file_path:
output_file = GeneratedFile._getOutputFile(self.java_file_path, check_only, temp_files)
file_parser.convert(output_file, self.java_header, self.java_footer, False, self.type)
self.java_output_file = output_file
def cmp(self):
if self.cpp_file_path:
if not filecmp.cmp(self.cpp_output_file, self.cpp_file_path):
return False
if self.java_file_path:
if not filecmp.cmp(self.java_output_file, self.java_file_path):
return False
return True
@staticmethod
def _getOutputFile(file_path, check_only, temp_files):
if not check_only:
return file_path
temp_file = createTempFile()
temp_files.append(temp_file)
return temp_file
def main():
parser = argparse.ArgumentParser(
description='Generate Java and C++ enums based on annotations in VehicleProperty.aidl')
parser.add_argument('--android_build_top', required=False, help='Path to ANDROID_BUILD_TOP')
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
if args.android_build_top:
android_top = args.android_build_top
vehiclePropertyUpdated = False
for preuload_file in args.preupload_files:
if preuload_file.endswith('VehicleProperty.aidl'):
vehiclePropertyUpdated = True
break
if not vehiclePropertyUpdated:
return
else:
android_top = os.environ['ANDROID_BUILD_TOP']
if not android_top:
print('ANDROID_BUILD_TOP is not in environmental variable, please run source and lunch ' +
'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
generated_files = []
change_mode = GeneratedFile('change_mode')
change_mode.setCppFilePath(os.path.join(android_top, CHANGE_MODE_CPP_FILE_PATH))
change_mode.setJavaFilePath(os.path.join(android_top, CHANGE_MODE_JAVA_FILE_PATH))
change_mode.setCppHeader(CHANGE_MODE_CPP_HEADER)
change_mode.setCppFooter(CPP_FOOTER)
change_mode.setJavaHeader(CHANGE_MODE_JAVA_HEADER)
change_mode.setJavaFooter(JAVA_FOOTER)
generated_files.append(change_mode)
access_mode = GeneratedFile('access_mode')
access_mode.setCppFilePath(os.path.join(android_top, ACCESS_CPP_FILE_PATH))
access_mode.setJavaFilePath(os.path.join(android_top, ACCESS_JAVA_FILE_PATH))
access_mode.setCppHeader(ACCESS_CPP_HEADER)
access_mode.setCppFooter(CPP_FOOTER)
access_mode.setJavaHeader(ACCESS_JAVA_HEADER)
access_mode.setJavaFooter(JAVA_FOOTER)
generated_files.append(access_mode)
enum_types = GeneratedFile('enum_types')
enum_types.setJavaFilePath(os.path.join(android_top, ENUM_JAVA_FILE_PATH))
enum_types.setJavaHeader(ENUM_JAVA_HEADER)
enum_types.setJavaFooter(JAVA_FOOTER)
generated_files.append(enum_types)
unit_type = GeneratedFile('unit_type')
unit_type.setJavaFilePath(os.path.join(android_top, UNITS_JAVA_FILE_PATH))
unit_type.setJavaHeader(UNITS_JAVA_HEADER)
unit_type.setJavaFooter(JAVA_FOOTER)
generated_files.append(unit_type)
version = GeneratedFile('version')
version.setCppFilePath(os.path.join(android_top, VERSION_CPP_FILE_PATH))
version.setCppHeader(VERSION_CPP_HEADER)
version.setCppFooter(CPP_FOOTER)
generated_files.append(version)
annotations = GeneratedFile('annotations')
annotations.setCppFilePath(os.path.join(android_top, ANNOTATIONS_CPP_FILE_PATH))
annotations.setJavaFilePath(os.path.join(android_top, ANNOTATIONS_JAVA_FILE_PATH))
annotations.setCppHeader(ANNOTATIONS_CPP_HEADER)
annotations.setCppFooter(CPP_FOOTER)
annotations.setJavaHeader(ANNOTATIONS_JAVA_HEADER)
annotations.setJavaFooter(JAVA_FOOTER)
generated_files.append(annotations)
temp_files = []
try:
for generated_file in generated_files:
generated_file.convert(f, args.check_only, temp_files)
if not args.check_only:
return
for generated_file in generated_files:
if not generated_file.cmp():
print('The generated enum files for VehicleProperty.aidl requires update, ')
print('Run \npython ' + android_top + '/' + SCRIPT_PATH)
sys.exit(1)
except Exception as e:
print('Error parsing VehicleProperty.aidl')
print(e)
sys.exit(1)
finally:
for file in temp_files:
os.remove(file)
if __name__ == '__main__':
main()