1#!/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. 41# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for 42# each partition. 43DIR_SEARCH_PATHS = { 44 '/system': ('SYSTEM',), 45 '/vendor': ('VENDOR', 'SYSTEM/vendor'), 46 '/product': ('PRODUCT', 'SYSTEM/product'), 47 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'), 48 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'), 49 # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files. 50} 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): 71 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split() 72 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus: 73 odm_skus += [''] 74 75 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split() 76 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \ 77 not vendor_skus: 78 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] 83 84 85def GetArgsForShippingApiLevel(info_dict): 86 shipping_api_level = info_dict['vendor.build.prop'].GetProp( 87 '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): 99 logger.info('Skipping kernel config checks because ' 100 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set') 101 return [] 102 103 return ['--kernel', '{}:{}'.format(version_path, config_path)] 104 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 ] 135 for device_path, real_path in sorted(dirmap.items()): 136 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() 145 if proc.returncode == 0: 146 logger.info("Command `%s` returns 'compatible'", ' '.join(command)) 147 elif out.strip() == "INCOMPATIBLE": 148 logger.info("Command `%s` returns 'incompatible'", ' '.join(command)) 149 success = False 150 else: 151 raise common.ExternalError( 152 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}" 153 .format(' '.join(command), proc.returncode, out, err)) 154 logger.info("stdout: %s", out) 155 logger.info("stderr: %s", err) 156 157 return success 158 159 160def GetVintfFileList(): 161 """ 162 Returns a list of VINTF metadata files that should be read from a target files 163 package before executing checkvintf. 164 """ 165 def PathToPatterns(path): 166 if path[-1] == '/': 167 path += '**' 168 169 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key 170 # is a prefix of path. In order to get find the correct prefix, sort the 171 # entries by decreasing length of their keys, so that we check if longer 172 # strings are prefixes before shorter strings. This is so that keys that 173 # are substrings of other keys (like /system vs /system_ext) are checked 174 # later, and we don't mistakenly mark a path that starts with /system_ext 175 # as starting with only /system. 176 for device_path, target_files_rel_paths in sorted(DIR_SEARCH_PATHS.items(), key=lambda i: len(i[0]), reverse=True): 177 if path.startswith(device_path): 178 suffix = path[len(device_path):] 179 return [rel_path + suffix for rel_path in target_files_rel_paths] 180 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' + 181 path) 182 183 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list']) 184 paths = out.strip().split('\n') 185 paths = sum((PathToPatterns(path) for path in paths if path), []) 186 return paths 187 188 189def CheckVintfFromTargetFiles(inp, info_dict=None): 190 """ 191 Checks VINTF metadata of a target files zip. 192 193 Args: 194 inp: path to the target files archive. 195 info_dict: The build-time info dict. If None, it will be loaded from inp. 196 197 Returns: 198 True if VINTF check is skipped or compatible, False if incompatible. Raise 199 a RuntimeError if any error occurs. 200 """ 201 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + UNZIP_PATTERN) 202 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict) 203 204 205def CheckVintf(inp, info_dict=None): 206 """ 207 Checks VINTF metadata of a target files zip or extracted target files 208 directory. 209 210 Args: 211 inp: path to the (possibly extracted) target files archive. 212 info_dict: The build-time info dict. If None, it will be loaded from inp. 213 214 Returns: 215 True if VINTF check is skipped or compatible, False if incompatible. Raise 216 a RuntimeError if any error occurs. 217 """ 218 if os.path.isdir(inp): 219 logger.info('Checking VINTF compatibility extracted target files...') 220 return CheckVintfFromExtractedTargetFiles(inp, info_dict) 221 222 if zipfile.is_zipfile(inp): 223 logger.info('Checking VINTF compatibility target files...') 224 return CheckVintfFromTargetFiles(inp, info_dict) 225 226 raise ValueError('{} is not a valid directory or zip file'.format(inp)) 227 228def CheckVintfIfTrebleEnabled(target_files, target_info): 229 """Checks compatibility info of the input target files. 230 231 Metadata used for compatibility verification is retrieved from target_zip. 232 233 Compatibility should only be checked for devices that have enabled 234 Treble support. 235 236 Args: 237 target_files: Path to zip file containing the source files to be included 238 for OTA. Can also be the path to extracted directory. 239 target_info: The BuildInfo instance that holds the target build info. 240 """ 241 242 # Will only proceed if the target has enabled the Treble support (as well as 243 # having a /vendor partition). 244 if not HasTrebleEnabled(target_files, target_info): 245 return 246 247 # Skip adding the compatibility package as a workaround for b/114240221. The 248 # compatibility will always fail on devices without qualified kernels. 249 if OPTIONS.skip_compatibility_check: 250 return 251 252 if not CheckVintf(target_files, target_info): 253 raise RuntimeError("VINTF compatibility check failed") 254 255def HasTrebleEnabled(target_files, target_info): 256 def HasVendorPartition(target_files): 257 if os.path.isdir(target_files): 258 return os.path.isdir(os.path.join(target_files, "VENDOR")) 259 if zipfile.is_zipfile(target_files): 260 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor") 261 raise ValueError("Unknown target_files argument") 262 263 return (HasVendorPartition(target_files) and 264 target_info.GetBuildProp("ro.treble.enabled") == "true") 265 266 267def HasPartition(target_files_zip, partition): 268 try: 269 target_files_zip.getinfo(partition.upper() + "/") 270 return True 271 except KeyError: 272 return False 273 274 275def main(argv): 276 args = common.ParseOptions(argv, __doc__) 277 if len(args) != 1: 278 common.Usage(__doc__) 279 sys.exit(1) 280 common.InitLogging() 281 if not CheckVintf(args[0]): 282 sys.exit(1) 283 284 285if __name__ == '__main__': 286 try: 287 common.CloseInheritedPipes() 288 main(sys.argv[1:]) 289 finally: 290 common.Cleanup() 291