• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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