1#!/usr/bin/env python3 2# Copyright (C) 2022 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15import sys 16import os 17import subprocess 18import re 19 20from tempfile import NamedTemporaryFile 21from pathlib import Path 22 23# Helper method that strips out the parameter names of methods. This will allow users to change 24# parameter names for hidden apis without mistaking them as having been removed. 25# [^ ]* --> Negation set on SPACE character. This wll match everything until a SPACE. 26# *?(?=\)) --> This means the character ')' will not be included in the match. 27# [^ (]*?(?=\)) --> This will handle the last parameter at the end of a method signature. 28# It excludes matching any '(' characters when there are no parameters, i.e. method(). 29# [^ ]*?(?=,) --> This will handle multiple parameters delimited by commas. 30def strip_param_names(api): 31 # get the arguments first 32 argGroup = re.search("\((.*)\)",api) 33 if argGroup is None: 34 return api 35 arg = argGroup.group(0) 36 new_arg = re.sub('[^ (]*?(?=\))|[^ ]*?(?=,)', "", arg) 37 return re.sub("\((.*)\)", new_arg, api) 38 39 40rootDir = os.getenv("ANDROID_BUILD_TOP") 41if rootDir is None or rootDir == "": 42 # env variable is not set. Then use the arg passed as Git root 43 rootDir = sys.argv[1] 44 45javaHomeDir = os.getenv("JAVA_HOME") 46if javaHomeDir is None or javaHomeDir == "": 47 if Path(rootDir + '/prebuilts/jdk/jdk17/linux-x86').is_dir(): 48 javaHomeDir = rootDir + "/prebuilts/jdk/jdk17/linux-x86" 49 else: 50 print("$JAVA_HOME is not set. Please use source build/envsetup.sh` in $ANDROID_BUILD_TOP") 51 sys.exit(1) 52 53# This generates a list of all classes. 54# Marker is set in GenerateApi.java class and should not be changed. 55marker = "Start-" 56options = ["--print-classes", "--print-hidden-apis", "--print-all-apis-with-constr", 57 "--print-incorrect-requires-api-usage-in-car-service", 58 "--print-addedin-without-requires-api-in-car-built-in"] 59 60java_cmd = javaHomeDir + "/bin/java -jar " + rootDir + \ 61 "/packages/services/Car/tools/GenericCarApiBuilder" \ 62 "/GenericCarApiBuilder.jar --root-dir " + rootDir + " " + " ".join(options) 63 64all_data = subprocess.check_output(java_cmd, shell=True).decode('utf-8').strip().split("\n") 65all_results = [] 66marker_index = [] 67for i in range(len(all_data)): 68 if all_data[i].replace(marker, "") in options: 69 marker_index.append(i) 70 71previous_mark = 0 72for mark in marker_index: 73 if mark > previous_mark: 74 all_results.append(all_data[previous_mark+1:mark]) 75 previous_mark = mark 76all_results.append(all_data[previous_mark+1:]) 77 78# Update this line when adding more options 79new_class_list, new_hidden_apis, all_apis = all_results[0], all_results[1], all_results[2] 80incorrect_requires_api_usage_in_car_service_errors = all_results[3] 81incorrect_addedin_api_usage_in_car_built_in_errors = all_results[4] 82new_hidden_apis = set(new_hidden_apis) 83all_apis = [strip_param_names(i) for i in all_apis] 84 85# Read current class list 86existing_car_api_classes_path = rootDir + "/packages/services/Car/tests/carservice_unit_test/" \ 87 "res/raw/car_api_classes.txt" 88existing_car_built_in_classes_path = rootDir + "/packages/services/Car/tests/" \ 89 "carservice_unit_test/res/raw/" \ 90 "car_built_in_api_classes.txt" 91existing_class_list = [] 92with open(existing_car_api_classes_path) as f: 93 existing_class_list.extend(f.read().splitlines()) 94with open(existing_car_built_in_classes_path) as f: 95 existing_class_list.extend(f.read().splitlines()) 96 97# Find the diff in both class list 98extra_new_classes = [i for i in new_class_list if i not in existing_class_list] 99extra_deleted_classes = [i for i in existing_class_list if i not in new_class_list] 100 101# Print error is there is any class added or removed without changing test 102error = "" 103if len(extra_deleted_classes) > 0: 104 error = error + "Following Classes are deleted \n" + "\n".join(extra_deleted_classes) 105if len(extra_new_classes) > 0: 106 error = error + "\n\nFollowing new classes are added \n" + "\n".join(extra_new_classes) 107 108if error != "": 109 print(error) 110 print("\nRun following command to generate classlist for annotation test") 111 print("cd $ANDROID_BUILD_TOP && m -j GenericCarApiBuilder && GenericCarApiBuilder " 112 "--update-classes") 113 print("\nThen run following test to make sure classes are properly annotated") 114 print("atest CarServiceUnitTest:android.car.AnnotationTest") 115 sys.exit(1) 116 117# read existing hidden APIs 118existing_hidden_apis_path = rootDir + "/packages/services/Car/tests/carservice_unit_test/res/raw" \ 119 "/car_hidden_apis.txt" 120 121# hidden_apis_previous_releases contains all the cumulative hidden apis added in previous releases. 122# If some hidden API was added in T-QPR and removed in master, then one should be able 123# to identify it. Accordingly, a new file will need to be generated for each release. 124hidden_apis_previous_releases_paths = [ 125 "/packages/services/Car/tests/carservice_unit_test/res/raw/car_hidden_apis_release_33.3.txt", 126 "/packages/services/Car/tests/carservice_unit_test/res/raw/car_hidden_apis_release_33.2.txt", 127 "/packages/services/Car/tests/carservice_unit_test/res/raw/car_hidden_apis_release_33.1.txt" 128] 129 130existing_hidden_apis = set() 131with open(existing_hidden_apis_path) as f: 132 existing_hidden_apis = set(f.read().splitlines()) 133 134hidden_apis_previous_releases = set() 135for path in hidden_apis_previous_releases_paths: 136 with open(rootDir + path) as f: 137 hidden_apis = set(f.read().splitlines()) 138 hidden_apis_previous_releases = hidden_apis_previous_releases.union(hidden_apis) 139 140# All new_hidden_apis should be in previous_hidden_apis. There can be some entry in 141# previous_hidden_apis 142# which is not in new_hidden_apis. It is okay as some APIs might have been promoted. 143modified_or_added_hidden_api = new_hidden_apis - existing_hidden_apis 144 145# TODO(b/266849922): Add a pre-submit test to also check for added or modified hidden apis, 146# since one could also bypass the repohook tool using --no-verify. 147if len(modified_or_added_hidden_api) > 0: 148 print("\nHidden APIs should not be added or modified. The following Hidden APIs were added or modified in this CL:") 149 print("\n".join(modified_or_added_hidden_api)) 150 print( 151 "\nIf adding a hidden API is necessary, please create a bug here: go/car-mainline-add-hidden-api." 152 "\nYou are responsible for maintaining the hidden API, which may include future deprecation or" 153 " upgrade of the hidden API. \nTo learn more about hidden API usage and removal in the Car stack please visit go/car-hidden-api-usage-removal." 154 "\nTo add a hidden API, please run the following command after creating the bug:") 155 print("\ncd $ANDROID_BUILD_TOP && m -j GenericCarApiBuilder && GenericCarApiBuilder " 156 "--update-hidden-apis") 157 print("\nPlease do not use \"no-verify\" to bypass this check. Reach out to gargmayank@ or" 158 " ethanalee@ if there is any confusion or repo upload is not working for you even after running the previous command.") 159 sys.exit(1) 160 161# Hidden APIs should not be removed. Check that any of the previously hidden apis still exist in the remaining apis. 162# This is different from hidden APIs that were upgraded to system or public APIs. 163removed_hidden_api = [] 164for api in hidden_apis_previous_releases: 165 if strip_param_names(api) not in all_apis: 166 removed_hidden_api.append(api) 167 168if len(removed_hidden_api) > 0: 169 print("\nHidden APIs cannot be removed as the Car stack is now a mainline module. The following Hidden APIs were removed:") 170 print("\n".join(removed_hidden_api)) 171 print("\nPlease do not use \"no-verify\" to bypass this check. " 172 "To learn more about hidden API deprecation and removal visit go/car-hidden-api-usage-removal. " 173 "\nReach out to gargmayank@ or ethanalee@ if you have any questions or concerns regarding " 174 "removing hidden APIs.") 175 sys.exit(1) 176 177# If a hidden API was upgraded to system or public API, the car_hidden_apis.txt should be updated to 178# reflect its upgrade. 179# Prior to this check, added and removed hidden APIs have been checked. At this point, the set 180# difference between existing_hidden_apis and new_hidden_apis indicates that some hidden APIs have 181# been upgraded." 182upgraded_hidden_apis = existing_hidden_apis - new_hidden_apis 183if len(upgraded_hidden_apis) > 0: 184 print("\nThe following hidden APIs were upgraded to either system or public APIs.") 185 print("\n".join(upgraded_hidden_apis)) 186 print("\nPlease run the following command to update: ") 187 print("\ncd $ANDROID_BUILD_TOP && m -j GenericCarApiBuilder && GenericCarApiBuilder " 188 "--update-hidden-apis") 189 print("\nReach out to gargmayank@ or ethanalee@ if you have any questions or concerns regarding " 190 "upgrading hidden APIs. Visit go/upgrade-hidden-api for more info.") 191 print("\n\n") 192 sys.exit(1) 193 194# Check if Car Service is throwing platform mismatch exception 195folder = rootDir + "/packages/services/Car/service/" 196files = [str(v) for v in list(Path(folder).rglob("*.java"))] 197errors = [] 198for f in files: 199 with open(f, "r") as tmp_f: 200 lines = tmp_f.readlines() 201 for i in range(len(lines)): 202 if "assertPlatformVersionAtLeast" in lines[i]: 203 errors.append("line: " + str(i) + ". assertPlatformVersionAtLeast used.") 204 if "PlatformVersionMismatchException" in lines[i]: 205 errors.append("line: " + str(i) + ". PlatformVersionMismatchException used.") 206if len(errors) > 0: 207 print("\nassertPlatformVersionAtLeast or PlatformVersionMismatchException should not be used in" 208 " car service. see go/car-mainline-version-assertion") 209 print("\n".join(errors)) 210 sys.exit(1) 211 212if len(incorrect_requires_api_usage_in_car_service_errors) > 0: 213 print("\nOnly non-public classes and methods can have RequiresApi annotation. Following public " 214 "methods/classes also have requiresAPI annotation which is not allowed. See " 215 "go/car-api-version-annotation#using-requiresapi-for-version-check") 216 print("\n".join(incorrect_requires_api_usage_in_car_service_errors)) 217 sys.exit(1) 218 219if len(incorrect_addedin_api_usage_in_car_built_in_errors) > 0: 220 print("\nFollowing APIs are missing RequiresAPI annotations. See " 221 "go/car-api-version-annotation#using-requiresapi-for-version-check") 222 print("\n".join(incorrect_addedin_api_usage_in_car_built_in_errors)) 223 sys.exit(1) 224