blob: 63a6cf76dbec7bcdf5385eb779d469c61948103c [file] [log] [blame]
Yifan Honge3ba82c2019-08-21 13:29:30 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2019 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"""
18Check VINTF compatibility from a target files package.
19
20Usage: check_target_files_vintf target_files
21
22target_files can be a ZIP file or an extracted target files directory.
23"""
24
25import logging
26import subprocess
27import sys
28import os
29import zipfile
30
31import common
32
33logger = logging.getLogger(__name__)
34
35OPTIONS = common.OPTIONS
36
37# Keys are paths that VINTF searches. Must keep in sync with libvintf's search
38# paths (VintfObject.cpp).
39# These paths are stored in different directories in target files package, so
40# we have to search for the correct path and tell checkvintf to remap them.
Yifan Hong2870d1e2019-12-19 13:58:00 -080041# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for
42# each partition.
Yifan Honge3ba82c2019-08-21 13:29:30 -070043DIR_SEARCH_PATHS = {
44 '/system': ('SYSTEM',),
45 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
46 '/product': ('PRODUCT', 'SYSTEM/product'),
Yifan Hong2870d1e2019-12-19 13:58:00 -080047 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
Yifan Hong9cbb6242019-12-19 13:56:59 -080048 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
Ramji Jiyani13a41372022-01-27 07:05:08 +000049 # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
Yifan Honge3ba82c2019-08-21 13:29:30 -070050}
51
52UNZIP_PATTERN = ['META/*', '*/build.prop']
53
54
55def GetDirmap(input_tmp):
56 dirmap = {}
57 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
58 for target_files_rel_path in target_files_rel_paths:
59 target_files_path = os.path.join(input_tmp, target_files_rel_path)
60 if os.path.isdir(target_files_path):
61 dirmap[device_path] = target_files_path
62 break
63 if device_path not in dirmap:
64 raise ValueError("Can't determine path for device path " + device_path +
65 ". Searched the following:" +
66 ("\n".join(target_files_rel_paths)))
67 return dirmap
68
69
70def GetArgsForSkus(info_dict):
Yifan Hong28ffd732020-03-13 13:11:10 -070071 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070072 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070073 odm_skus += ['']
Yifan Honge3ba82c2019-08-21 13:29:30 -070074
Yifan Hong28ffd732020-03-13 13:11:10 -070075 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070076 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \
77 not vendor_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070078 vendor_skus += ['']
79
80 return [['--property', 'ro.boot.product.hardware.sku=' + odm_sku,
81 '--property', 'ro.boot.product.vendor.sku=' + vendor_sku]
82 for odm_sku in odm_skus for vendor_sku in vendor_skus]
Yifan Honge3ba82c2019-08-21 13:29:30 -070083
Tianjie Xu0fde41e2020-05-09 05:24:18 +000084
Yifan Honge3ba82c2019-08-21 13:29:30 -070085def GetArgsForShippingApiLevel(info_dict):
Tianjie Xu0fde41e2020-05-09 05:24:18 +000086 shipping_api_level = info_dict['vendor.build.prop'].GetProp(
Yifan Honge3ba82c2019-08-21 13:29:30 -070087 'ro.product.first_api_level')
88 if not shipping_api_level:
89 logger.warning('Cannot determine ro.product.first_api_level')
90 return []
91 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
92
93
94def GetArgsForKernel(input_tmp):
95 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
96 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
97
98 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
Yifan Hong28ffd732020-03-13 13:11:10 -070099 logger.info('Skipping kernel config checks because '
Yifan Honge3ba82c2019-08-21 13:29:30 -0700100 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
101 return []
102
Yifan Hong72e78f22020-11-13 18:30:06 -0800103 return ['--kernel', '{}:{}'.format(version_path, config_path)]
Yifan Honge3ba82c2019-08-21 13:29:30 -0700104
105
106def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
107 """
108 Checks VINTF metadata of an extracted target files directory.
109
110 Args:
111 inp: path to the directory that contains the extracted target files archive.
112 info_dict: The build-time info dict. If None, it will be loaded from inp.
113
114 Returns:
115 True if VINTF check is skipped or compatible, False if incompatible. Raise
116 a RuntimeError if any error occurs.
117 """
118
119 if info_dict is None:
120 info_dict = common.LoadInfoDict(input_tmp)
121
122 if info_dict.get('vintf_enforce') != 'true':
123 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
124 return True
125
126 dirmap = GetDirmap(input_tmp)
127 args_for_skus = GetArgsForSkus(info_dict)
128 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
129 kernel_args = GetArgsForKernel(input_tmp)
130
131 common_command = [
132 'checkvintf',
133 '--check-compat',
134 ]
Cole Faustb820bcd2021-10-28 13:59:48 -0700135 for device_path, real_path in sorted(dirmap.items()):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700136 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
137 common_command += kernel_args
138 common_command += shipping_api_level_args
139
140 success = True
141 for sku_args in args_for_skus:
142 command = common_command + sku_args
143 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144 out, err = proc.communicate()
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000145 last_out_line = out.split()[-1] if out != "" else out
Yifan Honge3ba82c2019-08-21 13:29:30 -0700146 if proc.returncode == 0:
147 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000148 elif last_out_line.strip() == "INCOMPATIBLE":
Yifan Honge3ba82c2019-08-21 13:29:30 -0700149 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
150 success = False
151 else:
152 raise common.ExternalError(
153 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
154 .format(' '.join(command), proc.returncode, out, err))
155 logger.info("stdout: %s", out)
156 logger.info("stderr: %s", err)
157
158 return success
159
160
161def GetVintfFileList():
162 """
163 Returns a list of VINTF metadata files that should be read from a target files
164 package before executing checkvintf.
165 """
166 def PathToPatterns(path):
167 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100168 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700169
170 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
171 # is a prefix of path. In order to get find the correct prefix, sort the
172 # entries by decreasing length of their keys, so that we check if longer
173 # strings are prefixes before shorter strings. This is so that keys that
174 # are substrings of other keys (like /system vs /system_ext) are checked
175 # later, and we don't mistakenly mark a path that starts with /system_ext
176 # as starting with only /system.
177 for device_path, target_files_rel_paths in sorted(DIR_SEARCH_PATHS.items(), key=lambda i: len(i[0]), reverse=True):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700178 if path.startswith(device_path):
179 suffix = path[len(device_path):]
180 return [rel_path + suffix for rel_path in target_files_rel_paths]
181 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
182 path)
183
184 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
185 paths = out.strip().split('\n')
186 paths = sum((PathToPatterns(path) for path in paths if path), [])
187 return paths
188
189
190def CheckVintfFromTargetFiles(inp, info_dict=None):
191 """
192 Checks VINTF metadata of a target files zip.
193
194 Args:
195 inp: path to the target files archive.
196 info_dict: The build-time info dict. If None, it will be loaded from inp.
197
198 Returns:
199 True if VINTF check is skipped or compatible, False if incompatible. Raise
200 a RuntimeError if any error occurs.
201 """
202 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + UNZIP_PATTERN)
203 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
204
205
206def CheckVintf(inp, info_dict=None):
207 """
208 Checks VINTF metadata of a target files zip or extracted target files
209 directory.
210
211 Args:
212 inp: path to the (possibly extracted) target files archive.
213 info_dict: The build-time info dict. If None, it will be loaded from inp.
214
215 Returns:
216 True if VINTF check is skipped or compatible, False if incompatible. Raise
217 a RuntimeError if any error occurs.
218 """
219 if os.path.isdir(inp):
220 logger.info('Checking VINTF compatibility extracted target files...')
221 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
222
223 if zipfile.is_zipfile(inp):
224 logger.info('Checking VINTF compatibility target files...')
225 return CheckVintfFromTargetFiles(inp, info_dict)
226
227 raise ValueError('{} is not a valid directory or zip file'.format(inp))
228
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400229def CheckVintfIfTrebleEnabled(target_files, target_info):
230 """Checks compatibility info of the input target files.
231
232 Metadata used for compatibility verification is retrieved from target_zip.
233
234 Compatibility should only be checked for devices that have enabled
235 Treble support.
236
237 Args:
238 target_files: Path to zip file containing the source files to be included
239 for OTA. Can also be the path to extracted directory.
240 target_info: The BuildInfo instance that holds the target build info.
241 """
242
243 # Will only proceed if the target has enabled the Treble support (as well as
244 # having a /vendor partition).
245 if not HasTrebleEnabled(target_files, target_info):
246 return
247
248 # Skip adding the compatibility package as a workaround for b/114240221. The
249 # compatibility will always fail on devices without qualified kernels.
250 if OPTIONS.skip_compatibility_check:
251 return
252
253 if not CheckVintf(target_files, target_info):
254 raise RuntimeError("VINTF compatibility check failed")
255
256def HasTrebleEnabled(target_files, target_info):
257 def HasVendorPartition(target_files):
258 if os.path.isdir(target_files):
259 return os.path.isdir(os.path.join(target_files, "VENDOR"))
260 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400261 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400262 raise ValueError("Unknown target_files argument")
263
264 return (HasVendorPartition(target_files) and
265 target_info.GetBuildProp("ro.treble.enabled") == "true")
266
267
268def HasPartition(target_files_zip, partition):
269 try:
270 target_files_zip.getinfo(partition.upper() + "/")
271 return True
272 except KeyError:
273 return False
274
Yifan Honge3ba82c2019-08-21 13:29:30 -0700275
276def main(argv):
277 args = common.ParseOptions(argv, __doc__)
278 if len(args) != 1:
279 common.Usage(__doc__)
280 sys.exit(1)
281 common.InitLogging()
282 if not CheckVintf(args[0]):
283 sys.exit(1)
284
285
286if __name__ == '__main__':
287 try:
288 common.CloseInheritedPipes()
289 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700290 finally:
291 common.Cleanup()