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