blob: e7d3a183a4d3cba139759e5faa4321873704e413 [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
Rob Seymour3f1c9572022-07-28 21:56:28 +000025import json
Yifan Honge3ba82c2019-08-21 13:29:30 -070026import logging
Jooyung Haneb118212022-10-17 18:07:28 +090027import os
28import shutil
Yifan Honge3ba82c2019-08-21 13:29:30 -070029import subprocess
30import sys
Yifan Honge3ba82c2019-08-21 13:29:30 -070031import zipfile
32
33import common
34
35logger = logging.getLogger(__name__)
36
37OPTIONS = common.OPTIONS
38
39# Keys are paths that VINTF searches. Must keep in sync with libvintf's search
40# paths (VintfObject.cpp).
41# These paths are stored in different directories in target files package, so
42# we have to search for the correct path and tell checkvintf to remap them.
Yifan Hong2870d1e2019-12-19 13:58:00 -080043# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for
44# each partition.
Yifan Honge3ba82c2019-08-21 13:29:30 -070045DIR_SEARCH_PATHS = {
46 '/system': ('SYSTEM',),
47 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
48 '/product': ('PRODUCT', 'SYSTEM/product'),
Yifan Hong2870d1e2019-12-19 13:58:00 -080049 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
Yifan Hong9cbb6242019-12-19 13:56:59 -080050 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
Ramji Jiyani13a41372022-01-27 07:05:08 +000051 # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
Yifan Honge3ba82c2019-08-21 13:29:30 -070052}
53
54UNZIP_PATTERN = ['META/*', '*/build.prop']
55
56
57def GetDirmap(input_tmp):
58 dirmap = {}
59 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
60 for target_files_rel_path in target_files_rel_paths:
61 target_files_path = os.path.join(input_tmp, target_files_rel_path)
62 if os.path.isdir(target_files_path):
63 dirmap[device_path] = target_files_path
64 break
65 if device_path not in dirmap:
66 raise ValueError("Can't determine path for device path " + device_path +
67 ". Searched the following:" +
68 ("\n".join(target_files_rel_paths)))
69 return dirmap
70
71
72def GetArgsForSkus(info_dict):
Yifan Hong28ffd732020-03-13 13:11:10 -070073 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070074 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070075 odm_skus += ['']
Yifan Honge3ba82c2019-08-21 13:29:30 -070076
Yifan Hong28ffd732020-03-13 13:11:10 -070077 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070078 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \
79 not vendor_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070080 vendor_skus += ['']
81
82 return [['--property', 'ro.boot.product.hardware.sku=' + odm_sku,
83 '--property', 'ro.boot.product.vendor.sku=' + vendor_sku]
84 for odm_sku in odm_skus for vendor_sku in vendor_skus]
Yifan Honge3ba82c2019-08-21 13:29:30 -070085
Tianjie Xu0fde41e2020-05-09 05:24:18 +000086
Yifan Honge3ba82c2019-08-21 13:29:30 -070087def GetArgsForShippingApiLevel(info_dict):
Tianjie Xu0fde41e2020-05-09 05:24:18 +000088 shipping_api_level = info_dict['vendor.build.prop'].GetProp(
Yifan Honge3ba82c2019-08-21 13:29:30 -070089 'ro.product.first_api_level')
90 if not shipping_api_level:
91 logger.warning('Cannot determine ro.product.first_api_level')
92 return []
93 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
94
95
96def GetArgsForKernel(input_tmp):
97 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
98 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
99
100 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
Yifan Hong28ffd732020-03-13 13:11:10 -0700101 logger.info('Skipping kernel config checks because '
Yifan Honge3ba82c2019-08-21 13:29:30 -0700102 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
103 return []
104
Yifan Hong72e78f22020-11-13 18:30:06 -0800105 return ['--kernel', '{}:{}'.format(version_path, config_path)]
Yifan Honge3ba82c2019-08-21 13:29:30 -0700106
107
108def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
109 """
110 Checks VINTF metadata of an extracted target files directory.
111
112 Args:
113 inp: path to the directory that contains the extracted target files archive.
114 info_dict: The build-time info dict. If None, it will be loaded from inp.
115
116 Returns:
117 True if VINTF check is skipped or compatible, False if incompatible. Raise
118 a RuntimeError if any error occurs.
119 """
120
121 if info_dict is None:
122 info_dict = common.LoadInfoDict(input_tmp)
123
124 if info_dict.get('vintf_enforce') != 'true':
125 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
126 return True
127
Rob Seymour3f1c9572022-07-28 21:56:28 +0000128
Yifan Honge3ba82c2019-08-21 13:29:30 -0700129 dirmap = GetDirmap(input_tmp)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000130
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900131 # Simulate apexd with target-files.
132 # add a mapping('/apex' => ${input_tmp}/APEX) to dirmap
133 PrepareApexDirectory(input_tmp, dirmap)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000134
Yifan Honge3ba82c2019-08-21 13:29:30 -0700135 args_for_skus = GetArgsForSkus(info_dict)
136 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
137 kernel_args = GetArgsForKernel(input_tmp)
138
139 common_command = [
140 'checkvintf',
141 '--check-compat',
142 ]
Rob Seymour3f1c9572022-07-28 21:56:28 +0000143
Cole Faustb820bcd2021-10-28 13:59:48 -0700144 for device_path, real_path in sorted(dirmap.items()):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700145 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
146 common_command += kernel_args
147 common_command += shipping_api_level_args
148
149 success = True
150 for sku_args in args_for_skus:
151 command = common_command + sku_args
152 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 out, err = proc.communicate()
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000154 last_out_line = out.split()[-1] if out != "" else out
Yifan Honge3ba82c2019-08-21 13:29:30 -0700155 if proc.returncode == 0:
156 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000157 elif last_out_line.strip() == "INCOMPATIBLE":
Yifan Honge3ba82c2019-08-21 13:29:30 -0700158 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
159 success = False
160 else:
161 raise common.ExternalError(
162 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
163 .format(' '.join(command), proc.returncode, out, err))
164 logger.info("stdout: %s", out)
165 logger.info("stderr: %s", err)
166
167 return success
168
169
170def GetVintfFileList():
171 """
172 Returns a list of VINTF metadata files that should be read from a target files
173 package before executing checkvintf.
174 """
175 def PathToPatterns(path):
176 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100177 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700178
179 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
180 # is a prefix of path. In order to get find the correct prefix, sort the
181 # entries by decreasing length of their keys, so that we check if longer
182 # strings are prefixes before shorter strings. This is so that keys that
183 # are substrings of other keys (like /system vs /system_ext) are checked
184 # later, and we don't mistakenly mark a path that starts with /system_ext
185 # as starting with only /system.
186 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 -0700187 if path.startswith(device_path):
188 suffix = path[len(device_path):]
189 return [rel_path + suffix for rel_path in target_files_rel_paths]
190 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
191 path)
192
193 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
194 paths = out.strip().split('\n')
195 paths = sum((PathToPatterns(path) for path in paths if path), [])
196 return paths
197
Rob Seymour3f1c9572022-07-28 21:56:28 +0000198def GetVintfApexUnzipPatterns():
199 """ Build unzip pattern for APEXes. """
200 patterns = []
201 for target_files_rel_paths in DIR_SEARCH_PATHS.values():
202 for target_files_rel_path in target_files_rel_paths:
203 patterns.append(os.path.join(target_files_rel_path,"apex/*"))
204
205 return patterns
206
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900207
208def PrepareApexDirectory(inp, dirmap):
Jooyung Han8af44a92022-11-24 18:36:47 +0900209 """ Prepare /apex directory before running checkvintf
Rob Seymour3f1c9572022-07-28 21:56:28 +0000210
211 Apex binaries do not support dirmaps, in order to use these binaries we
212 need to move the APEXes from the extracted target file archives to the
213 expected device locations.
214
Jooyung Han8af44a92022-11-24 18:36:47 +0900215 This simulates how apexd activates APEXes.
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900216 1. create {inp}/APEX which is treated as a "/apex" on device.
217 2. invoke apexd_host with vendor APEXes.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000218 """
219
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900220 apex_dir = os.path.join(inp, 'APEX')
221 # checkvintf needs /apex dirmap
222 dirmap['/apex'] = apex_dir
Rob Seymour3f1c9572022-07-28 21:56:28 +0000223
Jooyung Han8af44a92022-11-24 18:36:47 +0900224 # Always create /apex directory for dirmap
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900225 os.makedirs(apex_dir)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000226
Jooyung Hanc9542ab2023-06-27 16:12:32 +0900227 # Invoke apexd_host to activate vendor APEXes for checkvintf
228 apex_host = os.path.join(OPTIONS.search_path, 'bin', 'apexd_host')
229 cmd = [apex_host, '--tool_path', OPTIONS.search_path]
230 cmd += ['--apex_path', dirmap['/apex']]
231 if '/vendor' in dirmap:
232 cmd += ['--vendor_path', dirmap['/vendor']]
233 common.RunAndCheckOutput(cmd)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000234
Yifan Honge3ba82c2019-08-21 13:29:30 -0700235
236def CheckVintfFromTargetFiles(inp, info_dict=None):
237 """
238 Checks VINTF metadata of a target files zip.
239
240 Args:
241 inp: path to the target files archive.
242 info_dict: The build-time info dict. If None, it will be loaded from inp.
243
244 Returns:
245 True if VINTF check is skipped or compatible, False if incompatible. Raise
246 a RuntimeError if any error occurs.
247 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000248 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700249 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
250
251
252def CheckVintf(inp, info_dict=None):
253 """
254 Checks VINTF metadata of a target files zip or extracted target files
255 directory.
256
257 Args:
258 inp: path to the (possibly extracted) target files archive.
259 info_dict: The build-time info dict. If None, it will be loaded from inp.
260
261 Returns:
262 True if VINTF check is skipped or compatible, False if incompatible. Raise
263 a RuntimeError if any error occurs.
264 """
265 if os.path.isdir(inp):
266 logger.info('Checking VINTF compatibility extracted target files...')
267 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
268
269 if zipfile.is_zipfile(inp):
270 logger.info('Checking VINTF compatibility target files...')
271 return CheckVintfFromTargetFiles(inp, info_dict)
272
273 raise ValueError('{} is not a valid directory or zip file'.format(inp))
274
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400275def CheckVintfIfTrebleEnabled(target_files, target_info):
276 """Checks compatibility info of the input target files.
277
278 Metadata used for compatibility verification is retrieved from target_zip.
279
280 Compatibility should only be checked for devices that have enabled
281 Treble support.
282
283 Args:
284 target_files: Path to zip file containing the source files to be included
285 for OTA. Can also be the path to extracted directory.
286 target_info: The BuildInfo instance that holds the target build info.
287 """
288
289 # Will only proceed if the target has enabled the Treble support (as well as
290 # having a /vendor partition).
291 if not HasTrebleEnabled(target_files, target_info):
292 return
293
294 # Skip adding the compatibility package as a workaround for b/114240221. The
295 # compatibility will always fail on devices without qualified kernels.
296 if OPTIONS.skip_compatibility_check:
297 return
298
299 if not CheckVintf(target_files, target_info):
300 raise RuntimeError("VINTF compatibility check failed")
301
302def HasTrebleEnabled(target_files, target_info):
303 def HasVendorPartition(target_files):
304 if os.path.isdir(target_files):
305 return os.path.isdir(os.path.join(target_files, "VENDOR"))
306 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400307 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400308 raise ValueError("Unknown target_files argument")
309
310 return (HasVendorPartition(target_files) and
311 target_info.GetBuildProp("ro.treble.enabled") == "true")
312
313
314def HasPartition(target_files_zip, partition):
315 try:
316 target_files_zip.getinfo(partition.upper() + "/")
317 return True
318 except KeyError:
319 return False
320
Yifan Honge3ba82c2019-08-21 13:29:30 -0700321
322def main(argv):
323 args = common.ParseOptions(argv, __doc__)
324 if len(args) != 1:
325 common.Usage(__doc__)
326 sys.exit(1)
327 common.InitLogging()
328 if not CheckVintf(args[0]):
329 sys.exit(1)
330
331
332if __name__ == '__main__':
333 try:
334 common.CloseInheritedPipes()
335 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700336 finally:
337 common.Cleanup()