1#!/usr/bin/python3 2 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""A script to generate Java files and CPP header files based on annotations in VehicleProperty.aidl 18 19 Need ANDROID_BUILD_TOP environmental variable to be set. This script will update 20 ChangeModeForVehicleProperty.h and AccessForVehicleProperty.h under generated_lib/version/cpp and 21 ChangeModeForVehicleProperty.java, AccessForVehicleProperty.java, EnumForVehicleProperty.java 22 UnitsForVehicleProperty.java under generated_lib/version/java. 23 24 Usage: 25 $ python generate_annotation_enums.py 26""" 27import argparse 28import filecmp 29import os 30import re 31import sys 32import tempfile 33 34# Keep this updated with the latest in-development property version. 35PROPERTY_VERSION = '4' 36 37PROP_AIDL_FILE_PATH = ('hardware/interfaces/automotive/vehicle/aidl_property/android/hardware/' + 38 'automotive/vehicle/VehicleProperty.aidl') 39GENERATED_LIB = ('hardware/interfaces/automotive/vehicle/aidl/generated_lib/' + PROPERTY_VERSION + 40 '/') 41CHANGE_MODE_CPP_FILE_PATH = GENERATED_LIB + '/cpp/ChangeModeForVehicleProperty.h' 42ACCESS_CPP_FILE_PATH = GENERATED_LIB + '/cpp/AccessForVehicleProperty.h' 43CHANGE_MODE_JAVA_FILE_PATH = GENERATED_LIB + '/java/ChangeModeForVehicleProperty.java' 44ACCESS_JAVA_FILE_PATH = GENERATED_LIB + '/java/AccessForVehicleProperty.java' 45ENUM_JAVA_FILE_PATH = GENERATED_LIB + '/java/EnumForVehicleProperty.java' 46UNITS_JAVA_FILE_PATH = GENERATED_LIB + '/java/UnitsForVehicleProperty.java' 47VERSION_CPP_FILE_PATH = GENERATED_LIB + '/cpp/VersionForVehicleProperty.h' 48SCRIPT_PATH = 'hardware/interfaces/automotive/vehicle/tools/generate_annotation_enums.py' 49 50TAB = ' ' 51RE_ENUM_START = re.compile('\s*enum VehicleProperty \{') 52RE_ENUM_END = re.compile('\s*\}\;') 53RE_COMMENT_BEGIN = re.compile('\s*\/\*\*?') 54RE_COMMENT_END = re.compile('\s*\*\/') 55RE_CHANGE_MODE = re.compile('\s*\* @change_mode (\S+)\s*') 56RE_VERSION = re.compile('\s*\* @version (\S+)\s*') 57RE_ACCESS = re.compile('\s*\* @access (\S+)\s*') 58RE_DATA_ENUM = re.compile('\s*\* @data_enum (\S+)\s*') 59RE_UNIT = re.compile('\s*\* @unit (\S+)\s+') 60RE_VALUE = re.compile('\s*(\w+)\s*=(.*)') 61 62LICENSE = """/* 63 * Copyright (C) 2023 The Android Open Source Project 64 * 65 * Licensed under the Apache License, Version 2.0 (the "License"); 66 * you may not use this file except in compliance with the License. 67 * You may obtain a copy of the License at 68 * 69 * http://www.apache.org/licenses/LICENSE-2.0 70 * 71 * Unless required by applicable law or agreed to in writing, software 72 * distributed under the License is distributed on an "AS IS" BASIS, 73 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 74 * See the License for the specific language governing permissions and 75 * limitations under the License. 76 */ 77 78/** 79 * DO NOT EDIT MANUALLY!!! 80 * 81 * Generated by tools/generate_annotation_enums.py. 82 */ 83 84// clang-format off 85 86""" 87 88CHANGE_MODE_CPP_HEADER = """#pragma once 89 90#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h> 91#include <aidl/android/hardware/automotive/vehicle/VehiclePropertyChangeMode.h> 92 93#include <unordered_map> 94 95namespace aidl { 96namespace android { 97namespace hardware { 98namespace automotive { 99namespace vehicle { 100 101std::unordered_map<VehicleProperty, VehiclePropertyChangeMode> ChangeModeForVehicleProperty = { 102""" 103 104CPP_FOOTER = """ 105}; 106 107} // namespace vehicle 108} // namespace automotive 109} // namespace hardware 110} // namespace android 111} // aidl 112""" 113 114ACCESS_CPP_HEADER = """#pragma once 115 116#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h> 117#include <aidl/android/hardware/automotive/vehicle/VehiclePropertyAccess.h> 118 119#include <unordered_map> 120 121namespace aidl { 122namespace android { 123namespace hardware { 124namespace automotive { 125namespace vehicle { 126 127std::unordered_map<VehicleProperty, VehiclePropertyAccess> AccessForVehicleProperty = { 128""" 129 130VERSION_CPP_HEADER = """#pragma once 131 132#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h> 133 134#include <unordered_map> 135 136namespace aidl { 137namespace android { 138namespace hardware { 139namespace automotive { 140namespace vehicle { 141 142std::unordered_map<VehicleProperty, int32_t> VersionForVehicleProperty = { 143""" 144 145CHANGE_MODE_JAVA_HEADER = """package android.hardware.automotive.vehicle; 146 147import java.util.Map; 148 149public final class ChangeModeForVehicleProperty { 150 151 public static final Map<Integer, Integer> values = Map.ofEntries( 152""" 153 154JAVA_FOOTER = """ 155 ); 156 157} 158""" 159 160ACCESS_JAVA_HEADER = """package android.hardware.automotive.vehicle; 161 162import java.util.Map; 163 164public final class AccessForVehicleProperty { 165 166 public static final Map<Integer, Integer> values = Map.ofEntries( 167""" 168 169ENUM_JAVA_HEADER = """package android.hardware.automotive.vehicle; 170 171import java.util.List; 172import java.util.Map; 173 174public final class EnumForVehicleProperty { 175 176 public static final Map<Integer, List<Class<?>>> values = Map.ofEntries( 177""" 178 179UNITS_JAVA_HEADER = """package android.hardware.automotive.vehicle; 180 181import java.util.Map; 182 183public final class UnitsForVehicleProperty { 184 185 public static final Map<Integer, Integer> values = Map.ofEntries( 186""" 187 188 189class PropertyConfig: 190 """Represents one VHAL property definition in VehicleProperty.aidl.""" 191 192 def __init__(self): 193 self.name = None 194 self.description = None 195 self.comment = None 196 self.change_mode = None 197 self.access_modes = [] 198 self.enum_types = [] 199 self.unit_type = None 200 self.version = None 201 202 def __repr__(self): 203 return self.__str__() 204 205 def __str__(self): 206 return ('PropertyConfig{{' + 207 'name: {}, description: {}, change_mode: {}, access_modes: {}, enum_types: {}' + 208 ', unit_type: {}, version: {}, comment: {}}}').format(self.name, self.description, 209 self.change_mode, self.access_modes, self.enum_types, self.unit_type, 210 self.version, self.comment) 211 212 213class FileParser: 214 215 def __init__(self): 216 self.configs = None 217 218 def parseFile(self, input_file): 219 """Parses the input VehicleProperty.aidl file into a list of property configs.""" 220 processing = False 221 in_comment = False 222 configs = [] 223 config = None 224 with open(input_file, 'r') as f: 225 for line in f.readlines(): 226 if RE_ENUM_START.match(line): 227 processing = True 228 elif RE_ENUM_END.match(line): 229 processing = False 230 if not processing: 231 continue 232 if RE_COMMENT_BEGIN.match(line): 233 in_comment = True 234 config = PropertyConfig() 235 description = '' 236 continue 237 238 if RE_COMMENT_END.match(line): 239 in_comment = False 240 if in_comment: 241 match = RE_CHANGE_MODE.match(line) 242 if match: 243 config.change_mode = match.group(1).replace('VehiclePropertyChangeMode.', '') 244 continue 245 match = RE_ACCESS.match(line) 246 if match: 247 config.access_modes.append(match.group(1).replace('VehiclePropertyAccess.', '')) 248 continue 249 match = RE_UNIT.match(line) 250 if match: 251 config.unit_type = match.group(1) 252 continue 253 match = RE_DATA_ENUM.match(line) 254 if match: 255 config.enum_types.append(match.group(1)) 256 continue 257 match = RE_VERSION.match(line) 258 if match: 259 if config.version != None: 260 raise Exception('Duplicate version annotation for property: ' + prop_name) 261 config.version = match.group(1) 262 continue 263 264 sline = line.strip() 265 if sline.startswith('*'): 266 # Remove the '*'. 267 sline = sline[1:].strip() 268 269 if not config.description: 270 # We reach an empty line of comment, the description part is ending. 271 if sline == '': 272 config.description = description 273 else: 274 if description != '': 275 description += ' ' 276 description += sline 277 else: 278 if not config.comment: 279 if sline != '': 280 # This is the first line for comment. 281 config.comment = sline 282 else: 283 if sline != '': 284 # Concat this line with the previous line's comment with a space. 285 config.comment += ' ' + sline 286 else: 287 # Treat empty line comment as a new line. 288 config.comment += '\n' 289 else: 290 match = RE_VALUE.match(line) 291 if match: 292 prop_name = match.group(1) 293 if prop_name == 'INVALID': 294 continue 295 if not config.change_mode: 296 raise Exception( 297 'No change_mode annotation for property: ' + prop_name) 298 if not config.access_modes: 299 raise Exception( 300 'No access_mode annotation for property: ' + prop_name) 301 if not config.version: 302 raise Exception( 303 'no version annotation for property: ' + prop_name) 304 config.name = prop_name 305 configs.append(config) 306 307 self.configs = configs 308 309 def convert(self, output, header, footer, cpp, field): 310 """Converts the property config file to C++/Java output file.""" 311 counter = 0 312 content = LICENSE + header 313 for config in self.configs: 314 if field == 'change_mode': 315 if cpp: 316 annotation = "VehiclePropertyChangeMode::" + config.change_mode 317 else: 318 annotation = "VehiclePropertyChangeMode." + config.change_mode 319 elif field == 'access_mode': 320 if cpp: 321 annotation = "VehiclePropertyAccess::" + config.access_modes[0] 322 else: 323 annotation = "VehiclePropertyAccess." + config.access_modes[0] 324 elif field == 'enum_types': 325 if len(config.enum_types) < 1: 326 continue; 327 if not cpp: 328 annotation = "List.of(" + ', '.join([class_name + ".class" for class_name in config.enum_types]) + ")" 329 elif field == 'unit_type': 330 if not config.unit_type: 331 continue 332 if not cpp: 333 annotation = config.unit_type 334 335 elif field == 'version': 336 if cpp: 337 annotation = config.version 338 else: 339 raise Exception('Unknown field: ' + field) 340 if counter != 0: 341 content += '\n' 342 if cpp: 343 content += (TAB + TAB + '{VehicleProperty::' + config.name + ', ' + 344 annotation + '},') 345 else: 346 content += (TAB + TAB + 'Map.entry(VehicleProperty.' + config.name + ', ' + 347 annotation + '),') 348 counter += 1 349 350 # Remove the additional ',' at the end for the Java file. 351 if not cpp: 352 content = content[:-1] 353 354 content += footer 355 356 with open(output, 'w') as f: 357 f.write(content) 358 359 def outputAsCsv(self, output): 360 content = 'name,description,change mode,access mode,enum type,unit type,comment\n' 361 for config in self.configs: 362 enum_types = None 363 if not config.enum_types: 364 enum_types = '/' 365 else: 366 enum_types = '/'.join(config.enum_types) 367 unit_type = config.unit_type 368 if not unit_type: 369 unit_type = '/' 370 access_modes = '' 371 comment = config.comment 372 if not comment: 373 comment = '' 374 content += '"{}","{}","{}","{}","{}","{}", "{}"\n'.format( 375 config.name, 376 # Need to escape quote as double quote. 377 config.description.replace('"', '""'), 378 config.change_mode, 379 '/'.join(config.access_modes), 380 enum_types, 381 unit_type, 382 comment.replace('"', '""')) 383 384 with open(output, 'w+') as f: 385 f.write(content) 386 387 388def createTempFile(): 389 f = tempfile.NamedTemporaryFile(delete=False); 390 f.close(); 391 return f.name 392 393 394class GeneratedFile: 395 396 def __init__(self, type): 397 self.type = type 398 self.cpp_file_path = None 399 self.java_file_path = None 400 self.cpp_header = None 401 self.java_header = None 402 self.cpp_footer = None 403 self.java_footer = None 404 self.cpp_output_file = None 405 self.java_output_file = None 406 407 def setCppFilePath(self, cpp_file_path): 408 self.cpp_file_path = cpp_file_path 409 410 def setJavaFilePath(self, java_file_path): 411 self.java_file_path = java_file_path 412 413 def setCppHeader(self, cpp_header): 414 self.cpp_header = cpp_header 415 416 def setCppFooter(self, cpp_footer): 417 self.cpp_footer = cpp_footer 418 419 def setJavaHeader(self, java_header): 420 self.java_header = java_header 421 422 def setJavaFooter(self, java_footer): 423 self.java_footer = java_footer 424 425 def convert(self, file_parser, check_only, temp_files): 426 if self.cpp_file_path: 427 output_file = GeneratedFile._getOutputFile(self.cpp_file_path, check_only, temp_files) 428 file_parser.convert(output_file, self.cpp_header, self.cpp_footer, True, self.type) 429 self.cpp_output_file = output_file 430 431 if self.java_file_path: 432 output_file = GeneratedFile._getOutputFile(self.java_file_path, check_only, temp_files) 433 file_parser.convert(output_file, self.java_header, self.java_footer, False, self.type) 434 self.java_output_file = output_file 435 436 def cmp(self): 437 if self.cpp_file_path: 438 if not filecmp.cmp(self.cpp_output_file, self.cpp_file_path): 439 return False 440 441 if self.java_file_path: 442 if not filecmp.cmp(self.java_output_file, self.java_file_path): 443 return False 444 445 return True 446 447 @staticmethod 448 def _getOutputFile(file_path, check_only, temp_files): 449 if not check_only: 450 return file_path 451 452 temp_file = createTempFile() 453 temp_files.append(temp_file) 454 return temp_file 455 456 457def main(): 458 parser = argparse.ArgumentParser( 459 description='Generate Java and C++ enums based on annotations in VehicleProperty.aidl') 460 parser.add_argument('--android_build_top', required=False, help='Path to ANDROID_BUILD_TOP') 461 parser.add_argument('--preupload_files', nargs='*', required=False, help='modified files') 462 parser.add_argument('--check_only', required=False, action='store_true', 463 help='only check whether the generated files need update') 464 parser.add_argument('--output_csv', required=False, 465 help='Path to the parsing result in CSV style, useful for doc generation') 466 args = parser.parse_args(); 467 android_top = None 468 output_folder = None 469 if args.android_build_top: 470 android_top = args.android_build_top 471 vehiclePropertyUpdated = False 472 for preuload_file in args.preupload_files: 473 if preuload_file.endswith('VehicleProperty.aidl'): 474 vehiclePropertyUpdated = True 475 break 476 if not vehiclePropertyUpdated: 477 return 478 else: 479 android_top = os.environ['ANDROID_BUILD_TOP'] 480 if not android_top: 481 print('ANDROID_BUILD_TOP is not in environmental variable, please run source and lunch ' + 482 'at the android root') 483 484 aidl_file = os.path.join(android_top, PROP_AIDL_FILE_PATH) 485 f = FileParser(); 486 f.parseFile(aidl_file) 487 488 if args.output_csv: 489 f.outputAsCsv(args.output_csv) 490 return 491 492 generated_files = [] 493 494 change_mode = GeneratedFile('change_mode') 495 change_mode.setCppFilePath(os.path.join(android_top, CHANGE_MODE_CPP_FILE_PATH)) 496 change_mode.setJavaFilePath(os.path.join(android_top, CHANGE_MODE_JAVA_FILE_PATH)) 497 change_mode.setCppHeader(CHANGE_MODE_CPP_HEADER) 498 change_mode.setCppFooter(CPP_FOOTER) 499 change_mode.setJavaHeader(CHANGE_MODE_JAVA_HEADER) 500 change_mode.setJavaFooter(JAVA_FOOTER) 501 generated_files.append(change_mode) 502 503 access_mode = GeneratedFile('access_mode') 504 access_mode.setCppFilePath(os.path.join(android_top, ACCESS_CPP_FILE_PATH)) 505 access_mode.setJavaFilePath(os.path.join(android_top, ACCESS_JAVA_FILE_PATH)) 506 access_mode.setCppHeader(ACCESS_CPP_HEADER) 507 access_mode.setCppFooter(CPP_FOOTER) 508 access_mode.setJavaHeader(ACCESS_JAVA_HEADER) 509 access_mode.setJavaFooter(JAVA_FOOTER) 510 generated_files.append(access_mode) 511 512 enum_types = GeneratedFile('enum_types') 513 enum_types.setJavaFilePath(os.path.join(android_top, ENUM_JAVA_FILE_PATH)) 514 enum_types.setJavaHeader(ENUM_JAVA_HEADER) 515 enum_types.setJavaFooter(JAVA_FOOTER) 516 generated_files.append(enum_types) 517 518 unit_type = GeneratedFile('unit_type') 519 unit_type.setJavaFilePath(os.path.join(android_top, UNITS_JAVA_FILE_PATH)) 520 unit_type.setJavaHeader(UNITS_JAVA_HEADER) 521 unit_type.setJavaFooter(JAVA_FOOTER) 522 generated_files.append(unit_type) 523 524 version = GeneratedFile('version') 525 version.setCppFilePath(os.path.join(android_top, VERSION_CPP_FILE_PATH)) 526 version.setCppHeader(VERSION_CPP_HEADER) 527 version.setCppFooter(CPP_FOOTER) 528 generated_files.append(version) 529 530 temp_files = [] 531 532 try: 533 for generated_file in generated_files: 534 generated_file.convert(f, args.check_only, temp_files) 535 536 if not args.check_only: 537 return 538 539 for generated_file in generated_files: 540 if not generated_file.cmp(): 541 print('The generated enum files for VehicleProperty.aidl requires update, ') 542 print('Run \npython ' + android_top + '/' + SCRIPT_PATH) 543 sys.exit(1) 544 except Exception as e: 545 print('Error parsing VehicleProperty.aidl') 546 print(e) 547 sys.exit(1) 548 finally: 549 for file in temp_files: 550 os.remove(file) 551 552 553if __name__ == '__main__': 554 main()